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

1678 lines
47 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Teleporter Object
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_obj_teleporter.h"
  9. #include "engine/IEngineSound.h"
  10. #include "tf_player.h"
  11. #include "tf_team.h"
  12. #include "tf_gamerules.h"
  13. #include "world.h"
  14. #include "explode.h"
  15. #include "particle_parse.h"
  16. #include "tf_gamestats.h"
  17. #include "tf_weapon_sniperrifle.h"
  18. #include "tf_fx.h"
  19. #include "props.h"
  20. #include "tf_objective_resource.h"
  21. #include "rtime.h"
  22. #include "tf_logic_player_destruction.h"
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include "tier0/memdbgon.h"
  25. // Ground placed version
  26. #define TELEPORTER_MODEL_ENTRANCE_PLACEMENT "models/buildables/teleporter_blueprint_enter.mdl"
  27. #define TELEPORTER_MODEL_EXIT_PLACEMENT "models/buildables/teleporter_blueprint_exit.mdl"
  28. #define TELEPORTER_MODEL_BUILDING "models/buildables/teleporter.mdl"
  29. #define TELEPORTER_MODEL_LIGHT "models/buildables/teleporter_light.mdl"
  30. #define TELEPORTER_MINS Vector( -24, -24, 0)
  31. #define TELEPORTER_MAXS Vector( 24, 24, 12)
  32. //-----------------------------------------------------------------------------
  33. // Purpose:
  34. //-----------------------------------------------------------------------------
  35. // Seconds it takes a teleporter to recharge
  36. int g_iTeleporterRechargeTimes[4] =
  37. {
  38. 0,
  39. 10,
  40. 5,
  41. 3
  42. };
  43. IMPLEMENT_SERVERCLASS_ST( CObjectTeleporter, DT_ObjectTeleporter )
  44. SendPropInt( SENDINFO(m_iState), 5 ),
  45. SendPropTime( SENDINFO(m_flRechargeTime) ),
  46. SendPropTime( SENDINFO(m_flCurrentRechargeDuration) ),
  47. SendPropInt( SENDINFO(m_iTimesUsed), 10, SPROP_UNSIGNED ),
  48. SendPropFloat( SENDINFO(m_flYawToExit), 8, 0, 0.0, 360.0f ),
  49. SendPropBool( SENDINFO(m_bMatchBuilding) ),
  50. END_SEND_TABLE()
  51. BEGIN_DATADESC( CObjectTeleporter )
  52. // keys
  53. DEFINE_KEYFIELD( m_iTeleportType, FIELD_INTEGER, "teleporterType" ),
  54. DEFINE_KEYFIELD( m_iszMatchingMapPlacedTeleporter, FIELD_STRING, "matchingTeleporter" ),
  55. // other
  56. DEFINE_THINKFUNC( TeleporterThink ),
  57. DEFINE_ENTITYFUNC( TeleporterTouch ),
  58. END_DATADESC()
  59. PRECACHE_REGISTER( obj_teleporter );
  60. #define TELEPORTER_THINK_CONTEXT "TeleporterContext"
  61. #define BUILD_TELEPORTER_DAMAGE 25 // how much damage an exploding teleporter can do
  62. #define BUILD_TELEPORTER_FADEOUT_TIME 0.25 // time to teleport a player out (teleporter with full health)
  63. #define BUILD_TELEPORTER_FADEIN_TIME 0.25 // time to teleport a player in (teleporter with full health)
  64. #define BUILD_TELEPORTER_NEXT_THINK 0.05
  65. #define BUILD_TELEPORTER_PLAYER_OFFSET 20 // how far above the origin of the teleporter to place a player
  66. #define BUILD_TELEPORTER_EFFECT_TIME 12.0 // seconds that player glows after teleporting
  67. ConVar tf_teleporter_fov_start( "tf_teleporter_fov_start", "120", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Starting FOV for teleporter zoom.", true, 1, false, 0 );
  68. 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 );
  69. LINK_ENTITY_TO_CLASS( obj_teleporter, CObjectTeleporter );
  70. //-----------------------------------------------------------------------------
  71. // Purpose: Teleport the passed player to our destination
  72. //-----------------------------------------------------------------------------
  73. void CObjectTeleporter::TeleporterSend( CTFPlayer *pPlayer )
  74. {
  75. if ( !pPlayer )
  76. return;
  77. SetTeleportingPlayer( pPlayer );
  78. pPlayer->m_Shared.AddCond( TF_COND_SELECTED_TO_TELEPORT );
  79. Vector origin = GetAbsOrigin();
  80. CPVSFilter filter( origin );
  81. int iTeam = pPlayer->GetTeamNumber();
  82. if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
  83. {
  84. if ( GetBuilder() && iTeam != GetBuilder()->GetTeamNumber() )
  85. {
  86. iTeam = GetBuilder()->GetTeamNumber();
  87. }
  88. }
  89. switch( iTeam )
  90. {
  91. case TF_TEAM_RED:
  92. TE_TFParticleEffect( filter, 0.0, "teleported_red", origin, vec3_angle );
  93. TE_TFParticleEffect( filter, 0.0, "player_sparkles_red", origin, vec3_angle, pPlayer, PATTACH_POINT );
  94. break;
  95. case TF_TEAM_BLUE:
  96. TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle );
  97. TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle, pPlayer, PATTACH_POINT );
  98. break;
  99. default:
  100. break;
  101. }
  102. EmitSound( "Building_Teleporter.Send" );
  103. SetState( TELEPORTER_STATE_SENDING );
  104. m_flMyNextThink = gpGlobals->curtime + 0.1;
  105. m_iTimesUsed++;
  106. m_hReservedForPlayer = NULL;
  107. // Strange - Teleports Provided to Allies
  108. if ( GetBuilder() && GetBuilder()->GetTeam() == pPlayer->GetTeam() )
  109. {
  110. // Strange Health Provided to Allies
  111. EconEntity_OnOwnerKillEaterEvent(
  112. dynamic_cast<CEconEntity *>( GetBuilder()->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA ) ),
  113. GetBuilder(),
  114. pPlayer,
  115. kKillEaterEvent_TeleportsProvided
  116. );
  117. if ( GetBuilder() != pPlayer &&
  118. TFGameRules() &&
  119. TFGameRules()->GameModeUsesUpgrades() &&
  120. TFGameRules()->State_Get() == GR_STATE_RND_RUNNING )
  121. {
  122. CTF_GameStats.Event_PlayerAwardBonusPoints( GetBuilder(), pPlayer, 10 );
  123. }
  124. }
  125. int iSpeedBoost = 0;
  126. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetBuilder(), iSpeedBoost, mod_teleporter_speed_boost );
  127. if ( iSpeedBoost )
  128. {
  129. pPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, 4.f );
  130. }
  131. }
  132. //-----------------------------------------------------------------------------
  133. // Purpose: Receive a teleporting player
  134. //-----------------------------------------------------------------------------
  135. void CObjectTeleporter::TeleporterReceive( CTFPlayer *pPlayer, float flDelay )
  136. {
  137. if ( !pPlayer )
  138. return;
  139. SetTeleportingPlayer( pPlayer );
  140. Vector origin = GetAbsOrigin();
  141. CPVSFilter filter( origin );
  142. int iTeam = pPlayer->GetTeamNumber();
  143. if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
  144. {
  145. if ( GetBuilder() && iTeam != GetBuilder()->GetTeamNumber() )
  146. {
  147. iTeam = GetBuilder()->GetTeamNumber();
  148. }
  149. }
  150. if ( GetBuilder() )
  151. {
  152. pPlayer->m_Shared.SetTeamTeleporterUsed( GetBuilder()->GetTeamNumber() );
  153. }
  154. switch( iTeam )
  155. {
  156. case TF_TEAM_RED:
  157. TE_TFParticleEffect( filter, 0.0, "teleportedin_red", origin, vec3_angle );
  158. break;
  159. case TF_TEAM_BLUE:
  160. TE_TFParticleEffect( filter, 0.0, "teleportedin_blue", origin, vec3_angle );
  161. break;
  162. default:
  163. break;
  164. }
  165. EmitSound( "Building_Teleporter.Receive" );
  166. SetState( TELEPORTER_STATE_RECEIVING );
  167. m_flMyNextThink = gpGlobals->curtime + BUILD_TELEPORTER_FADEOUT_TIME;
  168. m_iTimesUsed++;
  169. }
  170. //-----------------------------------------------------------------------------
  171. // Purpose:
  172. //-----------------------------------------------------------------------------
  173. CObjectTeleporter::CObjectTeleporter()
  174. {
  175. int iHealth = GetMaxHealthForCurrentLevel();
  176. SetMaxHealth( iHealth );
  177. SetHealth( iHealth );
  178. UseClientSideAnimation();
  179. SetType( OBJ_TELEPORTER );
  180. m_bMatchBuilding.Set( false );
  181. m_iTeleportType = TTYPE_NONE;
  182. m_flCurrentRechargeDuration = 0.0f;
  183. m_flRechargeTime = 0.0f;
  184. ListenForGameEvent( "player_spawn" );
  185. ListenForGameEvent( "player_team" );
  186. }
  187. //-----------------------------------------------------------------------------
  188. // Purpose:
  189. //-----------------------------------------------------------------------------
  190. void CObjectTeleporter::Spawn()
  191. {
  192. SetSolid( SOLID_BBOX );
  193. m_takedamage = DAMAGE_NO;
  194. SetState( TELEPORTER_STATE_BUILDING );
  195. m_flNextEnemyTouchHint = gpGlobals->curtime;
  196. m_flYawToExit = 0;
  197. if ( IsEntrance() )
  198. {
  199. SetModel( TELEPORTER_MODEL_ENTRANCE_PLACEMENT );
  200. }
  201. else
  202. {
  203. SetModel( TELEPORTER_MODEL_EXIT_PLACEMENT );
  204. }
  205. m_iUpgradeLevel = 1;
  206. BaseClass::Spawn();
  207. }
  208. //-----------------------------------------------------------------------------
  209. // Purpose:
  210. //-----------------------------------------------------------------------------
  211. void CObjectTeleporter::UpdateOnRemove()
  212. {
  213. if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
  214. {
  215. TFObjectiveResource()->DecrementTeleporterCount();
  216. }
  217. BaseClass::UpdateOnRemove();
  218. }
  219. //-----------------------------------------------------------------------------
  220. // Purpose:
  221. //-----------------------------------------------------------------------------
  222. void CObjectTeleporter::FirstSpawn()
  223. {
  224. int iHealth = GetMaxHealthForCurrentLevel();
  225. SetMaxHealth( iHealth );
  226. SetHealth( iHealth );
  227. BaseClass::FirstSpawn();
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Purpose:
  231. //-----------------------------------------------------------------------------
  232. void CObjectTeleporter::SetObjectMode( int iVal )
  233. {
  234. #ifdef STAGING_ONLY
  235. int iSpeedPad = 0;
  236. if ( GetBuilder() )
  237. {
  238. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetBuilder(), iSpeedPad, teleporter_is_speedpad );
  239. if ( iSpeedPad )
  240. {
  241. SetTeleporterType( TTYPE_SPEEDPAD );
  242. }
  243. }
  244. if ( !iSpeedPad )
  245. {
  246. switch ( iVal )
  247. {
  248. case MODE_TELEPORTER_ENTRANCE:
  249. SetTeleporterType( TTYPE_ENTRANCE );
  250. break;
  251. case MODE_TELEPORTER_EXIT:
  252. SetTeleporterType( TTYPE_EXIT );
  253. break;
  254. }
  255. }
  256. #else
  257. if ( iVal == MODE_TELEPORTER_ENTRANCE )
  258. {
  259. SetTeleporterType( TTYPE_ENTRANCE );
  260. }
  261. else
  262. {
  263. SetTeleporterType( TTYPE_EXIT );
  264. }
  265. #endif
  266. BaseClass::SetObjectMode( iVal );
  267. }
  268. //-----------------------------------------------------------------------------
  269. int CObjectTeleporter::GetUpgradeMetalRequired()
  270. {
  271. #ifdef STAGING_ONLY
  272. // STAGING_ENGY
  273. int iSpeedPad = 0;
  274. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetBuilder(), iSpeedPad, teleporter_is_speedpad )
  275. if ( iSpeedPad )
  276. {
  277. return 100;
  278. }
  279. #endif
  280. int nCost = GetObjectInfo( GetType() )->m_UpgradeCost;
  281. float flCostMod = 1.f;
  282. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flCostMod, mod_teleporter_cost );
  283. if ( flCostMod != 1.f )
  284. {
  285. nCost *= flCostMod;
  286. }
  287. return nCost;
  288. }
  289. //-----------------------------------------------------------------------------
  290. // Purpose:
  291. //-----------------------------------------------------------------------------
  292. void CObjectTeleporter::SetModel( const char *pModel )
  293. {
  294. BaseClass::SetModel( pModel );
  295. // Reset this after model change
  296. UTIL_SetSize( this, TELEPORTER_MINS, TELEPORTER_MAXS );
  297. CreateBuildPoints();
  298. ReattachChildren();
  299. m_iDirectionBodygroup = FindBodygroupByName( "teleporter_direction" );
  300. m_iBlurBodygroup = FindBodygroupByName( "teleporter_blur" );
  301. if ( m_iBlurBodygroup >= 0 )
  302. {
  303. SetBodygroup( m_iBlurBodygroup, 0 );
  304. }
  305. }
  306. void CObjectTeleporter::InitializeMapPlacedObject( void )
  307. {
  308. BaseClass::InitializeMapPlacedObject();
  309. SetObjectMode( IsEntrance() ? MODE_TELEPORTER_ENTRANCE : MODE_TELEPORTER_EXIT );
  310. #ifdef STAGING_ONLY
  311. if ( GetTeleporterType() == TTYPE_SPEEDPAD )
  312. return;
  313. #endif
  314. m_hMatchingTeleporter = dynamic_cast<CObjectTeleporter*>( gEntList.FindEntityByName( NULL, m_iszMatchingMapPlacedTeleporter.ToCStr() ) );
  315. // Select the teleporter with the most upgrade
  316. if ( m_hMatchingTeleporter.Get() )
  317. {
  318. bool bFrom = (m_hMatchingTeleporter->GetUpgradeLevel() > GetUpgradeLevel() || m_hMatchingTeleporter->GetUpgradeMetal() > GetUpgradeMetal() );
  319. CopyUpgradeStateToMatch( m_hMatchingTeleporter, bFrom );
  320. }
  321. }
  322. //-----------------------------------------------------------------------------
  323. // Purpose: Start building the object
  324. //-----------------------------------------------------------------------------
  325. bool CObjectTeleporter::StartBuilding( CBaseEntity *pBuilder )
  326. {
  327. SetStartBuildingModel();
  328. if ( GetTeleporterType() == TTYPE_NONE )
  329. {
  330. if ( GetObjectMode() == MODE_TELEPORTER_ENTRANCE )
  331. {
  332. SetTeleporterType( TTYPE_ENTRANCE );
  333. }
  334. else
  335. {
  336. SetTeleporterType( TTYPE_EXIT );
  337. }
  338. }
  339. return BaseClass::StartBuilding( pBuilder );
  340. }
  341. void CObjectTeleporter::SetStartBuildingModel( void )
  342. {
  343. SetState( TELEPORTER_STATE_BUILDING );
  344. SetModel( TELEPORTER_MODEL_BUILDING );
  345. }
  346. //-----------------------------------------------------------------------------
  347. //
  348. //-----------------------------------------------------------------------------
  349. bool CObjectTeleporter::IsPlacementPosValid( void )
  350. {
  351. bool bResult = BaseClass::IsPlacementPosValid();
  352. if ( !bResult )
  353. {
  354. return false;
  355. }
  356. // m_vecBuildOrigin is the proposed build origin
  357. // start above the teleporter position
  358. Vector vecTestPos = m_vecBuildOrigin;
  359. vecTestPos.z += TELEPORTER_MAXS.z;
  360. // make sure we can fit a player on top in this pos
  361. trace_t tr;
  362. UTIL_TraceHull( vecTestPos, vecTestPos, VEC_HULL_MIN, VEC_HULL_MAX, MASK_SOLID | CONTENTS_PLAYERCLIP, this, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
  363. return ( tr.fraction >= 1.0 );
  364. }
  365. //-----------------------------------------------------------------------------
  366. //
  367. //-----------------------------------------------------------------------------
  368. void CObjectTeleporter::OnGoActive( void )
  369. {
  370. Assert( GetBuilder() || m_bWasMapPlaced );
  371. SetModel( TELEPORTER_MODEL_LIGHT );
  372. SetActivity( ACT_OBJ_IDLE );
  373. SetContextThink( &CObjectTeleporter::TeleporterThink, gpGlobals->curtime + 0.1, TELEPORTER_THINK_CONTEXT );
  374. SetTouch( &CObjectTeleporter::TeleporterTouch );
  375. SetState( TELEPORTER_STATE_IDLE );
  376. BaseClass::OnGoActive();
  377. SetPlaybackRate( 0.0f );
  378. m_flLastStateChangeTime = 0.0f; // used as a flag to initialize the playback rate to 0 in the first DeterminePlaybackRate
  379. // match our partner's maxhealth
  380. if ( IsMatchingTeleporterReady() )
  381. {
  382. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  383. if ( pMatch )
  384. {
  385. UpdateMaxHealth( pMatch->GetMaxHealth() );
  386. }
  387. }
  388. }
  389. //-----------------------------------------------------------------------------
  390. // Purpose:
  391. //-----------------------------------------------------------------------------
  392. void CObjectTeleporter::Precache()
  393. {
  394. BaseClass::Precache();
  395. // Precache Object Models
  396. int iModelIndex;
  397. PrecacheModel( TELEPORTER_MODEL_ENTRANCE_PLACEMENT );
  398. PrecacheModel( TELEPORTER_MODEL_EXIT_PLACEMENT );
  399. iModelIndex = PrecacheModel( TELEPORTER_MODEL_BUILDING );
  400. PrecacheGibsForModel( iModelIndex );
  401. iModelIndex = PrecacheModel( TELEPORTER_MODEL_LIGHT );
  402. PrecacheGibsForModel( iModelIndex );
  403. // Bread models
  404. int nRange = TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS;
  405. for( int i = 0; i < nRange; ++i )
  406. {
  407. if ( g_pszBreadModels[i] && *g_pszBreadModels[i] )
  408. {
  409. PrecacheModel( g_pszBreadModels[i] );
  410. }
  411. }
  412. // Precache Sounds
  413. PrecacheScriptSound( "Building_Teleporter.Ready" );
  414. PrecacheScriptSound( "Building_Teleporter.Send" );
  415. PrecacheScriptSound( "Building_Teleporter.Receive" );
  416. PrecacheScriptSound( "Building_Teleporter.SpinLevel1" );
  417. PrecacheScriptSound( "Building_Teleporter.SpinLevel2" );
  418. PrecacheScriptSound( "Building_Teleporter.SpinLevel3" );
  419. PrecacheParticleSystem( "teleporter_red_charged" );
  420. PrecacheParticleSystem( "teleporter_blue_charged" );
  421. PrecacheParticleSystem( "teleporter_red_entrance" );
  422. PrecacheParticleSystem( "teleporter_blue_entrance" );
  423. PrecacheParticleSystem( "teleporter_red_exit" );
  424. PrecacheParticleSystem( "teleporter_blue_exit" );
  425. PrecacheParticleSystem( "teleporter_arms_circle_red" );
  426. PrecacheParticleSystem( "teleporter_arms_circle_blue" );
  427. PrecacheParticleSystem( "tpdamage_1" );
  428. PrecacheParticleSystem( "tpdamage_2" );
  429. PrecacheParticleSystem( "tpdamage_3" );
  430. PrecacheParticleSystem( "tpdamage_4" );
  431. PrecacheParticleSystem( "teleported_red" );
  432. PrecacheParticleSystem( "player_sparkles_red" );
  433. PrecacheParticleSystem( "teleported_blue" );
  434. PrecacheParticleSystem( "player_sparkles_blue" );
  435. PrecacheParticleSystem( "teleportedin_red" );
  436. PrecacheParticleSystem( "teleportedin_blue" );
  437. PrecacheParticleSystem( "teleporter_arms_circle_red_blink" );
  438. PrecacheParticleSystem( "teleporter_arms_circle_blue_blink" );
  439. #ifdef STAGING_ONLY
  440. // STAGING ENGY
  441. PrecacheScriptSound( "Building_Speedpad.BoostStart" );
  442. PrecacheScriptSound( "Building_Speedpad.BoostStop" );
  443. #endif
  444. }
  445. //-----------------------------------------------------------------------------
  446. // Purpose:
  447. //-----------------------------------------------------------------------------
  448. bool CObjectTeleporter::PlayerCanBeTeleported( CTFPlayer *pPlayer )
  449. {
  450. if ( !pPlayer )
  451. return false;
  452. if ( pPlayer->HasTheFlag() )
  453. {
  454. if ( !CTFPlayerDestructionLogic::GetRobotDestructionLogic() || ( CTFPlayerDestructionLogic::GetRobotDestructionLogic()->GetType() != CTFPlayerDestructionLogic::TYPE_PLAYER_DESTRUCTION ) )
  455. return false;
  456. }
  457. CTFPlayer *pBuilder = GetBuilder();
  458. if ( !pBuilder && m_bWasMapPlaced == false )
  459. return false;
  460. if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) )
  461. return true;
  462. if ( pBuilder && pBuilder->GetTeamNumber() != pPlayer->GetTeamNumber() )
  463. return false;
  464. if ( m_bWasMapPlaced && GetTeamNumber() != pPlayer->GetTeamNumber() )
  465. return false;
  466. if ( TFGameRules() && TFGameRules()->IsPasstimeMode() && pPlayer->m_Shared.HasPasstimeBall() )
  467. return false;
  468. return true;
  469. }
  470. //-----------------------------------------------------------------------------
  471. // Purpose:
  472. //-----------------------------------------------------------------------------
  473. void CObjectTeleporter::StartTouch( CBaseEntity *pOther )
  474. {
  475. BaseClass::StartTouch(pOther);
  476. if ( m_hReservedForPlayer == pOther )
  477. {
  478. m_flReserveAfterTouchUntil = 0;
  479. }
  480. }
  481. //-----------------------------------------------------------------------------
  482. // Purpose:
  483. //-----------------------------------------------------------------------------
  484. void CObjectTeleporter::EndTouch( CBaseEntity *pOther )
  485. {
  486. BaseClass::EndTouch(pOther);
  487. if ( m_hReservedForPlayer == pOther )
  488. {
  489. // Players can push the reserved player off the teleporter. So after the player falls off the teleporter
  490. // we allow him to continue reserving it for a short time.
  491. m_flReserveAfterTouchUntil = gpGlobals->curtime + 2.0;
  492. }
  493. }
  494. //-----------------------------------------------------------------------------
  495. // Purpose:
  496. //-----------------------------------------------------------------------------
  497. void CObjectTeleporter::TeleporterTouch( CBaseEntity *pOther )
  498. {
  499. if ( IsDisabled() )
  500. {
  501. return;
  502. }
  503. // if it's not a player, ignore
  504. if ( !pOther->IsPlayer() )
  505. return;
  506. CTFPlayer *pPlayer = ToTFPlayer( pOther );
  507. if ( !PlayerCanBeTeleported( pPlayer ) )
  508. {
  509. // are we able to teleport?
  510. if ( pPlayer->HasTheFlag() )
  511. {
  512. // If they have the flag, print a warning that you can't tele with the flag
  513. CSingleUserRecipientFilter filter( pPlayer );
  514. TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_NO_TELE_WITH_FLAG );
  515. }
  516. else if ( pPlayer->m_Shared.HasPasstimeBall() )
  517. {
  518. CSingleUserRecipientFilter filter( pPlayer );
  519. TFGameRules()->SendHudNotification( filter, HUD_NOTIFY_PASSTIME_NO_TELE );
  520. }
  521. if ( m_hReservedForPlayer == pPlayer )
  522. {
  523. m_hReservedForPlayer = NULL;
  524. }
  525. return;
  526. }
  527. #ifdef STAGING_ONLY
  528. // STAGING_ENGY
  529. // For Speed Teleporters
  530. if ( IsSpeedPad() )
  531. {
  532. ApplySpeedBoost( pPlayer );
  533. return;
  534. }
  535. #endif
  536. int iBiDirectional = 0;
  537. if ( GetOwner() )
  538. {
  539. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iBiDirectional, bidirectional_teleport );
  540. }
  541. if ( IsEntrance() || iBiDirectional == 1 )
  542. {
  543. // Reserve ourselves for the first player who touches us.
  544. // Players can push the reserved player off the teleporter. So after the player falls off the teleporter
  545. // we allow him to continue reserving it for a short time.
  546. bool bSetReserved = !m_hReservedForPlayer;
  547. if ( !bSetReserved )
  548. {
  549. bSetReserved = ( !PlayerCanBeTeleported(m_hReservedForPlayer) || !m_hReservedForPlayer->IsAlive() ||
  550. (m_flReserveAfterTouchUntil != 0 && m_flReserveAfterTouchUntil < gpGlobals->curtime) );
  551. }
  552. if ( bSetReserved )
  553. {
  554. m_hReservedForPlayer = pPlayer;
  555. m_flReserveAfterTouchUntil = 0;
  556. }
  557. // If we're reserved for another player, ignore me
  558. if ( m_hReservedForPlayer != pPlayer )
  559. return;
  560. if ( ( m_iState == TELEPORTER_STATE_READY ) )
  561. {
  562. // get the velocity of the player touching the teleporter
  563. if ( pPlayer->GetAbsVelocity().LengthSqr() < (5.0*5.0) )
  564. {
  565. CObjectTeleporter *pDest = GetMatchingTeleporter();
  566. if ( pDest )
  567. {
  568. TeleporterSend( pPlayer );
  569. }
  570. }
  571. else
  572. {
  573. // If it's been some time since we went active, and the reserved player still
  574. // hasn't teleported, we clear his reservation to prevent griefing.
  575. if ( gpGlobals->curtime - m_flLastStateChangeTime > 3.0 )
  576. {
  577. m_hReservedForPlayer = NULL;
  578. }
  579. }
  580. }
  581. }
  582. }
  583. #ifdef STAGING_ONLY
  584. //STAGING_ENGY
  585. //-----------------------------------------------------------------------------
  586. void CObjectTeleporter::ApplySpeedBoost( CTFPlayer *pPlayer )
  587. {
  588. if ( m_iState != TELEPORTER_STATE_READY )
  589. return;
  590. Vector origin = GetAbsOrigin();
  591. CPVSFilter filter( origin );
  592. int iTeam = pPlayer->GetTeamNumber();
  593. if ( pPlayer->IsPlayerClass( TF_CLASS_SPY ) && pPlayer->m_Shared.InCond( TF_COND_DISGUISED ) )
  594. {
  595. if ( GetBuilder() && iTeam != GetBuilder()->GetTeamNumber() )
  596. {
  597. iTeam = GetBuilder()->GetTeamNumber();
  598. }
  599. }
  600. switch ( iTeam )
  601. {
  602. case TF_TEAM_RED:
  603. TE_TFParticleEffect( filter, 0.0, "teleported_red", origin, vec3_angle );
  604. TE_TFParticleEffect( filter, 0.0, "player_sparkles_red", origin, vec3_angle, pPlayer, PATTACH_POINT );
  605. break;
  606. case TF_TEAM_BLUE:
  607. TE_TFParticleEffect( filter, 0.0, "teleported_blue", origin, vec3_angle );
  608. TE_TFParticleEffect( filter, 0.0, "player_sparkles_blue", origin, vec3_angle, pPlayer, PATTACH_POINT );
  609. break;
  610. default:
  611. break;
  612. }
  613. float flUpgrade = (float)GetUpgradeLevel();
  614. pPlayer->m_Shared.AddCond( TF_COND_NO_COMBAT_SPEED_BOOST, 3.0f + flUpgrade );
  615. SetState( TELEPORTER_STATE_RECHARGING );
  616. EmitSound( "Building_Speedpad.BoostStart" );
  617. m_flCurrentRechargeDuration = 2.0f - ( flUpgrade / 3.0f );
  618. m_flRechargeTime = gpGlobals->curtime + ( BUILD_TELEPORTER_FADEOUT_TIME + BUILD_TELEPORTER_FADEIN_TIME + m_flCurrentRechargeDuration );
  619. m_flMyNextThink = gpGlobals->curtime + m_flCurrentRechargeDuration;
  620. }
  621. #endif
  622. //-----------------------------------------------------------------------------
  623. // Purpose:
  624. //-----------------------------------------------------------------------------
  625. bool CObjectTeleporter::Command_Repair( CTFPlayer *pActivator, float flRepairMod )
  626. {
  627. float flTargetHeal = 100.0f * flRepairMod;
  628. int iAmountToHeal = MIN( flTargetHeal, GetMaxHealth() - GetHealth() );
  629. // repair the building
  630. int iRepairCost = ceil( (float)( iAmountToHeal ) * 0.2f );
  631. TRACE_OBJECT( UTIL_VarArgs( "%0.2f CObjectTeleporter::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime,
  632. GetHealth(),
  633. GetMaxHealth(),
  634. iRepairCost ) );
  635. if ( iRepairCost > 0 )
  636. {
  637. if ( iRepairCost > pActivator->GetBuildResources() )
  638. {
  639. iRepairCost = pActivator->GetBuildResources();
  640. }
  641. pActivator->RemoveBuildResources( iRepairCost );
  642. int nHealthToAdd = iRepairCost * 5;
  643. float flNewHealth = MIN( GetMaxHealth(), GetHealth() + nHealthToAdd );
  644. SetHealth( flNewHealth );
  645. // add the same amount of health to our match
  646. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  647. if ( pMatch )
  648. {
  649. pMatch->AddHealth( nHealthToAdd );
  650. }
  651. return ( iRepairCost > 0 );
  652. }
  653. else
  654. {
  655. // see if our match needs repairing
  656. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  657. if ( pMatch && !pMatch->IsBuilding() )
  658. {
  659. iAmountToHeal = MIN( flTargetHeal, pMatch->GetMaxHealth() - pMatch->GetHealth() );
  660. // repair the building
  661. iRepairCost = ceil( (float)( iAmountToHeal ) * 0.2f );
  662. TRACE_OBJECT( UTIL_VarArgs( "%0.2f CObjectTeleporter::Command_Repair ( %f / %d ) - cost = %d\n", gpGlobals->curtime,
  663. pMatch->GetHealth(),
  664. pMatch->GetMaxHealth(),
  665. iRepairCost ) );
  666. if ( iRepairCost > 0 )
  667. {
  668. if ( iRepairCost > pActivator->GetBuildResources() )
  669. {
  670. iRepairCost = pActivator->GetBuildResources();
  671. }
  672. pActivator->RemoveBuildResources( iRepairCost );
  673. int nHealthToAdd = iRepairCost * 5;
  674. float flNewHealth = MIN( pMatch->GetMaxHealth(), pMatch->GetHealth() + nHealthToAdd );
  675. pMatch->SetHealth( flNewHealth );
  676. return ( iRepairCost > 0 );
  677. }
  678. }
  679. }
  680. return false;
  681. }
  682. //-----------------------------------------------------------------------------
  683. // Purpose: Is this teleporter connected and functional? (ie: not sapped, disabled, upgrading, unconnected, etc)
  684. //-----------------------------------------------------------------------------
  685. bool CObjectTeleporter::IsReady( void )
  686. {
  687. #ifdef STAGING_ONLY
  688. if ( !IsMatchingTeleporterReady() && !IsSpeedPad() )
  689. #else
  690. if ( !IsMatchingTeleporterReady() )
  691. #endif
  692. return false;
  693. return GetState() != TELEPORTER_STATE_BUILDING && !IsUpgrading() && !IsDisabled();
  694. }
  695. //-----------------------------------------------------------------------------
  696. // Purpose:
  697. //-----------------------------------------------------------------------------
  698. bool CObjectTeleporter::IsMatchingTeleporterReady( void )
  699. {
  700. if ( m_hMatchingTeleporter.Get() == NULL )
  701. {
  702. m_hMatchingTeleporter = FindMatch();
  703. }
  704. if ( m_hMatchingTeleporter &&
  705. m_hMatchingTeleporter->GetState() != TELEPORTER_STATE_BUILDING &&
  706. !m_hMatchingTeleporter->IsUpgrading() &&
  707. !m_hMatchingTeleporter->IsDisabled() )
  708. return true;
  709. return false;
  710. }
  711. //-----------------------------------------------------------------------------
  712. // Purpose: Returns true if we are in the process of teleporting the given player
  713. //-----------------------------------------------------------------------------
  714. bool CObjectTeleporter::IsSendingPlayer( CTFPlayer *pPlayer )
  715. {
  716. return ( GetState() == TELEPORTER_STATE_SENDING && m_hTeleportingPlayer == pPlayer );
  717. }
  718. //-----------------------------------------------------------------------------
  719. // Purpose:
  720. //-----------------------------------------------------------------------------
  721. bool CObjectTeleporter::CheckUpgradeOnHit( CTFPlayer *pPlayer )
  722. {
  723. if ( BaseClass::CheckUpgradeOnHit( pPlayer ) )
  724. {
  725. CopyUpgradeStateToMatch( GetMatchingTeleporter(), false );
  726. return true;
  727. }
  728. return false;
  729. }
  730. //-----------------------------------------------------------------------------
  731. // Purpose:
  732. //-----------------------------------------------------------------------------
  733. void CObjectTeleporter::CopyUpgradeStateToMatch( CObjectTeleporter *pMatch, bool bFrom )
  734. {
  735. // Copy our upgrade state to the matching teleporter
  736. if ( pMatch )
  737. {
  738. if ( bFrom )
  739. {
  740. pMatch->CopyUpgradeStateToMatch( pMatch, false );
  741. }
  742. else
  743. {
  744. pMatch->m_iHighestUpgradeLevel = m_iHighestUpgradeLevel;
  745. pMatch->m_iUpgradeLevel = m_iUpgradeLevel;
  746. pMatch->m_iUpgradeMetal = m_iUpgradeMetal;
  747. pMatch->m_iUpgradeMetalRequired = m_iUpgradeMetalRequired;
  748. pMatch->m_nDefaultUpgradeLevel = m_nDefaultUpgradeLevel;
  749. pMatch->m_flUpgradeCompleteTime = m_flUpgradeCompleteTime;
  750. }
  751. }
  752. }
  753. //-----------------------------------------------------------------------------
  754. // Purpose:
  755. //-----------------------------------------------------------------------------
  756. CObjectTeleporter *CObjectTeleporter::GetMatchingTeleporter( void )
  757. {
  758. #ifdef STAGING_ONLY
  759. if ( GetTeleporterType() == TTYPE_SPEEDPAD )
  760. return NULL;
  761. #endif
  762. return m_hMatchingTeleporter.Get();
  763. }
  764. void CObjectTeleporter::DeterminePlaybackRate( void )
  765. {
  766. float flPlaybackRate = GetPlaybackRate();
  767. bool bWasBelowFullSpeed = ( flPlaybackRate < 1.0f );
  768. if ( IsBuilding() )
  769. {
  770. // Fall back to standard object building to handle reverse sappers without duplicating code
  771. BaseClass::DeterminePlaybackRate();
  772. return;
  773. }
  774. else if ( IsPlacing() )
  775. {
  776. SetPlaybackRate( 1.0f );
  777. }
  778. else
  779. {
  780. float flFrameTime = 0.1; // BaseObjectThink delay
  781. switch( m_iState )
  782. {
  783. case TELEPORTER_STATE_READY:
  784. {
  785. // spin up to 1.0 from whatever we're at, at some high rate
  786. flPlaybackRate = Approach( 1.0f, flPlaybackRate, 0.5f * flFrameTime );
  787. }
  788. break;
  789. case TELEPORTER_STATE_RECHARGING:
  790. {
  791. // Recharge - spin down to low and back up to full speed over the recharge time
  792. float flTotalTime = m_flCurrentRechargeDuration;
  793. float flFirstStage = flTotalTime * 0.4;
  794. float flSecondStage = flTotalTime * 0.6;
  795. // 0 -> 4, spin to low
  796. // 4 -> 6, stay at low
  797. // 6 -> 10, spin to 1.0
  798. float flTimeSinceChange = gpGlobals->curtime - m_flLastStateChangeTime;
  799. float flLowSpinSpeed = 0.15f;
  800. if ( flTimeSinceChange <= flFirstStage )
  801. {
  802. flPlaybackRate = RemapVal( gpGlobals->curtime,
  803. m_flLastStateChangeTime,
  804. m_flLastStateChangeTime + flFirstStage,
  805. 1.0f,
  806. flLowSpinSpeed );
  807. }
  808. else if ( flTimeSinceChange > flFirstStage && flTimeSinceChange <= flSecondStage )
  809. {
  810. flPlaybackRate = flLowSpinSpeed;
  811. }
  812. else
  813. {
  814. flPlaybackRate = RemapVal( gpGlobals->curtime,
  815. m_flLastStateChangeTime + flSecondStage,
  816. m_flLastStateChangeTime + flTotalTime,
  817. flLowSpinSpeed,
  818. 1.0f );
  819. }
  820. }
  821. break;
  822. default:
  823. {
  824. if ( m_flLastStateChangeTime <= 0.0f )
  825. {
  826. flPlaybackRate = 0.0f;
  827. }
  828. else
  829. {
  830. // lost connect - spin down to 0.0 from whatever we're at, slowish rate
  831. flPlaybackRate = Approach( 0.0f, flPlaybackRate, 0.25f * flFrameTime );
  832. }
  833. }
  834. break;
  835. }
  836. // Always spin when the teleporter is done building
  837. if ( TFGameRules()->IsMannVsMachineMode() && GetTeamNumber() == TF_TEAM_PVE_INVADERS )
  838. {
  839. flPlaybackRate = 1.f;
  840. }
  841. SetPlaybackRate( flPlaybackRate );
  842. }
  843. bool bBelowFullSpeed = ( GetPlaybackRate() < 1.0f );
  844. if ( m_iBlurBodygroup >= 0 && bBelowFullSpeed != bWasBelowFullSpeed )
  845. {
  846. if ( bBelowFullSpeed )
  847. {
  848. SetBodygroup( m_iBlurBodygroup, 0 ); // turn off blur bodygroup
  849. }
  850. else
  851. {
  852. SetBodygroup( m_iBlurBodygroup, 1 ); // turn on blur bodygroup
  853. }
  854. }
  855. StudioFrameAdvance();
  856. }
  857. //-----------------------------------------------------------------------------
  858. // Purpose: Teleport a player to us
  859. //-----------------------------------------------------------------------------
  860. void CObjectTeleporter::RecieveTeleportingPlayer( CTFPlayer* pTeleportingPlayer )
  861. {
  862. if ( !pTeleportingPlayer || IsMarkedForDeletion() )
  863. return;
  864. // get the position we'll move the player to
  865. Vector newPosition = GetAbsOrigin();
  866. newPosition.z += TELEPORTER_MAXS.z + 1;
  867. // Telefrag anyone in the way
  868. CBaseEntity *pEnts[256];
  869. Vector mins, maxs;
  870. Vector expand( 4, 4, 4 );
  871. mins = newPosition + VEC_HULL_MIN - expand;
  872. maxs = newPosition + VEC_HULL_MAX + expand;
  873. // move the player
  874. if ( pTeleportingPlayer )
  875. {
  876. CUtlVector<CBaseEntity*> hPlayersToKill;
  877. bool bClear = true;
  878. // Telefrag any players in the way
  879. int numEnts = UTIL_EntitiesInBox( pEnts, 256, mins, maxs, 0 );
  880. if ( numEnts )
  881. {
  882. //Iterate through the list and check the results
  883. for ( int i = 0; i < numEnts && bClear; i++ )
  884. {
  885. if ( pEnts[i] == NULL )
  886. continue;
  887. if ( pEnts[i] == this )
  888. continue;
  889. // kill players
  890. if ( pEnts[i]->IsPlayer() && ( pEnts[i]->GetTeamNumber() >= FIRST_GAME_TEAM ) )
  891. {
  892. if ( !pTeleportingPlayer->InSameTeam( pEnts[i] ) && ( pTeleportingPlayer->GetTeamNumber() >= FIRST_GAME_TEAM ) )
  893. {
  894. hPlayersToKill.AddToTail( pEnts[i] );
  895. }
  896. continue;
  897. }
  898. if ( pEnts[i]->IsBaseObject() )
  899. continue;
  900. // Solid entities will prevent a teleport
  901. if ( pEnts[i]->IsSolid() && pEnts[i]->ShouldCollide( pTeleportingPlayer->GetCollisionGroup(), MASK_SOLID ) &&
  902. g_pGameRules->ShouldCollide( pTeleportingPlayer->GetCollisionGroup(), pEnts[i]->GetCollisionGroup() ) )
  903. {
  904. // HACK to solve the problem of building teleporter exits in CDynamicProp entities at
  905. // the end of maps like Badwater that have the VPhysics explosions when the point is capped
  906. CDynamicProp *pProp = dynamic_cast<CDynamicProp *>( pEnts[i] );
  907. if ( !pProp )
  908. {
  909. CBaseProjectile *pProjectile = dynamic_cast<CBaseProjectile *>( pEnts[i] );
  910. if ( !pProjectile )
  911. {
  912. bClear = false;
  913. }
  914. }
  915. else
  916. {
  917. if ( !pProp->IsEffectActive( EF_NODRAW ) )
  918. {
  919. // We're going to teleport into something solid. Abort & destroy this exit.
  920. bClear = false;
  921. }
  922. }
  923. // need to make sure we're really overlapping geometry and not just overlapping the bounding boxes
  924. if ( !bClear )
  925. {
  926. Ray_t ray;
  927. ray.Init( newPosition, newPosition, VEC_HULL_MIN - expand, VEC_HULL_MAX + expand );
  928. trace_t trace;
  929. enginetrace->ClipRayToEntity( ray, MASK_PLAYERSOLID, pEnts[i], &trace );
  930. if ( trace.fraction >= 1.0f )
  931. {
  932. // not overlapping geometry so reset our check
  933. bClear = true;
  934. }
  935. }
  936. }
  937. }
  938. }
  939. if ( bClear )
  940. {
  941. // Telefrag all enemy players we've found
  942. for ( int player = 0; player < hPlayersToKill.Count(); player++ )
  943. {
  944. hPlayersToKill[player]->TakeDamage( CTakeDamageInfo( pTeleportingPlayer, pTeleportingPlayer, 1000, DMG_CRUSH, TF_DMG_CUSTOM_TELEFRAG ) );
  945. }
  946. pTeleportingPlayer->Teleport( &newPosition, &(GetAbsAngles()), &vec3_origin );
  947. // Unzoom if we are a sniper zoomed!
  948. pTeleportingPlayer->m_Shared.InstantlySniperUnzoom();
  949. pTeleportingPlayer->SetFOV( pTeleportingPlayer, 0, tf_teleporter_fov_time.GetFloat(), tf_teleporter_fov_start.GetInt() );
  950. color32 fadeColor = {255,255,255,100};
  951. UTIL_ScreenFade( pTeleportingPlayer, fadeColor, 0.25, 0.4, FFADE_IN );
  952. // 1/20 of te time teleport bread -- except for Soldier who does it 1/3 of the time.
  953. int nMax = pTeleportingPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_SOLDIER ? 2 : 19;
  954. if ( RandomInt( 0, nMax ) == 0 )
  955. {
  956. SpawnBread( pTeleportingPlayer );
  957. }
  958. }
  959. else
  960. {
  961. DetonateObject();
  962. }
  963. }
  964. }
  965. //-----------------------------------------------------------------------------
  966. // Purpose:
  967. //-----------------------------------------------------------------------------
  968. void CObjectTeleporter::TeleporterThink( void )
  969. {
  970. if ( IsCarried() )
  971. return;
  972. SetContextThink( &CObjectTeleporter::TeleporterThink, gpGlobals->curtime + BUILD_TELEPORTER_NEXT_THINK, TELEPORTER_THINK_CONTEXT );
  973. // At any point, if our match is not ready, revert to IDLE
  974. #ifdef STAGING_ONLY
  975. if ( IsDisabled() || ( IsMatchingTeleporterReady() == false && !IsSpeedPad() ))
  976. #else
  977. if ( IsDisabled() || IsMatchingTeleporterReady() == false )
  978. #endif
  979. {
  980. if ( GetState() != TELEPORTER_STATE_IDLE && GetState() != TELEPORTER_STATE_UPGRADING )
  981. {
  982. SetState( TELEPORTER_STATE_IDLE );
  983. ShowDirectionArrow( false );
  984. }
  985. return;
  986. }
  987. if ( m_flMyNextThink && m_flMyNextThink > gpGlobals->curtime )
  988. return;
  989. // pMatch is not NULL and is not building
  990. #ifdef STAGING_ONLY
  991. CObjectTeleporter *pMatch = NULL;
  992. if ( !IsSpeedPad() )
  993. {
  994. pMatch = GetMatchingTeleporter();
  995. Assert( pMatch );
  996. Assert( pMatch->m_iState != TELEPORTER_STATE_BUILDING );
  997. }
  998. #else
  999. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  1000. #endif
  1001. int iBiDirectional = 0;
  1002. if ( GetOwner() )
  1003. {
  1004. CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), iBiDirectional, bidirectional_teleport );
  1005. }
  1006. switch ( m_iState )
  1007. {
  1008. // Teleporter is not yet active, do nothing
  1009. case TELEPORTER_STATE_BUILDING:
  1010. case TELEPORTER_STATE_UPGRADING:
  1011. ShowDirectionArrow( false );
  1012. break;
  1013. default:
  1014. case TELEPORTER_STATE_IDLE:
  1015. // Do we have a match that is active?
  1016. #ifdef STAGING_ONLY
  1017. if ( IsMatchingTeleporterReady() || IsSpeedPad() )
  1018. #else
  1019. if ( IsMatchingTeleporterReady() )
  1020. #endif
  1021. {
  1022. SetState( TELEPORTER_STATE_READY );
  1023. EmitSound( "Building_Teleporter.Ready" );
  1024. if ( IsEntrance() || iBiDirectional == 1 )
  1025. {
  1026. ShowDirectionArrow( true );
  1027. }
  1028. }
  1029. break;
  1030. case TELEPORTER_STATE_READY:
  1031. if ( IsEntrance() || iBiDirectional == 1 )
  1032. {
  1033. ShowDirectionArrow( true );
  1034. }
  1035. break;
  1036. case TELEPORTER_STATE_SENDING:
  1037. {
  1038. pMatch->TeleporterReceive( m_hTeleportingPlayer, 1.0 );
  1039. m_flCurrentRechargeDuration = (float)g_iTeleporterRechargeTimes[GetUpgradeLevel()];
  1040. if ( !m_bWasMapPlaced )
  1041. {
  1042. CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), m_flCurrentRechargeDuration, mult_teleporter_recharge_rate );
  1043. }
  1044. m_flRechargeTime = gpGlobals->curtime + ( BUILD_TELEPORTER_FADEOUT_TIME + BUILD_TELEPORTER_FADEIN_TIME + m_flCurrentRechargeDuration );
  1045. // change state to recharging...
  1046. SetState( TELEPORTER_STATE_RECHARGING );
  1047. }
  1048. break;
  1049. case TELEPORTER_STATE_RECEIVING:
  1050. {
  1051. RecieveTeleportingPlayer( m_hTeleportingPlayer.Get() );
  1052. SetState( TELEPORTER_STATE_RECEIVING_RELEASE );
  1053. m_flMyNextThink = gpGlobals->curtime + ( BUILD_TELEPORTER_FADEIN_TIME );
  1054. }
  1055. break;
  1056. case TELEPORTER_STATE_RECEIVING_RELEASE:
  1057. {
  1058. CTFPlayer *pTeleportingPlayer = m_hTeleportingPlayer.Get();
  1059. if ( pTeleportingPlayer )
  1060. {
  1061. pTeleportingPlayer->TeleportEffect();
  1062. pTeleportingPlayer->m_Shared.RemoveCond( TF_COND_SELECTED_TO_TELEPORT );
  1063. CTF_GameStats.Event_PlayerUsedTeleport( GetBuilder(), pTeleportingPlayer );
  1064. pTeleportingPlayer->SpeakConceptIfAllowed( MP_CONCEPT_TELEPORTED );
  1065. IGameEvent * event = gameeventmanager->CreateEvent( "player_teleported" );
  1066. if ( event )
  1067. {
  1068. event->SetInt( "userid", pTeleportingPlayer->GetUserID() );
  1069. event->SetInt( "builderid", GetBuilder() ? GetBuilder()->GetUserID() : 0 );
  1070. if ( GetMatchingTeleporter() )
  1071. {
  1072. event->SetFloat( "dist", GetMatchingTeleporter()->GetAbsOrigin().DistTo( GetAbsOrigin() ) );
  1073. }
  1074. else
  1075. {
  1076. event->SetFloat( "dist", 0 );
  1077. }
  1078. gameeventmanager->FireEvent( event );
  1079. }
  1080. }
  1081. // reset the pointers to the player now that we're done teleporting
  1082. SetTeleportingPlayer( NULL );
  1083. pMatch->SetTeleportingPlayer( NULL );
  1084. SetState( TELEPORTER_STATE_RECHARGING );
  1085. m_flCurrentRechargeDuration = (float)g_iTeleporterRechargeTimes[GetUpgradeLevel()];
  1086. m_flMyNextThink = gpGlobals->curtime + m_flCurrentRechargeDuration;
  1087. }
  1088. break;
  1089. case TELEPORTER_STATE_RECHARGING:
  1090. // If we are finished recharging, go active
  1091. if ( gpGlobals->curtime > m_flRechargeTime )
  1092. {
  1093. SetState( TELEPORTER_STATE_READY );
  1094. EmitSound( "Building_Teleporter.Ready" );
  1095. }
  1096. break;
  1097. }
  1098. }
  1099. //-----------------------------------------------------------------------------
  1100. // Purpose:
  1101. //-----------------------------------------------------------------------------
  1102. void CObjectTeleporter::FinishedBuilding( void )
  1103. {
  1104. BaseClass::FinishedBuilding();
  1105. if ( GetTeamNumber() == TF_TEAM_PVE_INVADERS )
  1106. {
  1107. TFObjectiveResource()->IncrementTeleporterCount();
  1108. }
  1109. SetActivity( ACT_OBJ_RUNNING );
  1110. SetPlaybackRate( 0.0f );
  1111. }
  1112. void CObjectTeleporter::SetState( int state )
  1113. {
  1114. if ( state != m_iState )
  1115. {
  1116. m_iState = state;
  1117. m_flLastStateChangeTime = gpGlobals->curtime;
  1118. }
  1119. }
  1120. void CObjectTeleporter::ShowDirectionArrow( bool bShow )
  1121. {
  1122. if ( bShow != m_bShowDirectionArrow )
  1123. {
  1124. if ( m_iDirectionBodygroup >= 0 )
  1125. {
  1126. SetBodygroup( m_iDirectionBodygroup, bShow ? 1 : 0 );
  1127. }
  1128. m_bShowDirectionArrow = bShow;
  1129. if ( bShow )
  1130. {
  1131. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  1132. Assert( pMatch );
  1133. Vector vecToOwner = pMatch->GetAbsOrigin() - GetAbsOrigin();
  1134. QAngle angleToExit;
  1135. VectorAngles( vecToOwner, Vector(0,0,1), angleToExit );
  1136. angleToExit -= GetAbsAngles();
  1137. // pose param is flipped and backwards, adjust.
  1138. m_flYawToExit = anglemod( -angleToExit.y + 180 );
  1139. }
  1140. }
  1141. }
  1142. //-----------------------------------------------------------------------------
  1143. // Purpose:
  1144. //-----------------------------------------------------------------------------
  1145. int CObjectTeleporter::DrawDebugTextOverlays(void)
  1146. {
  1147. int text_offset = BaseClass::DrawDebugTextOverlays();
  1148. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  1149. {
  1150. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  1151. char tempstr[512];
  1152. // match
  1153. Q_snprintf( tempstr, sizeof( tempstr ), "Match Found: %s", ( pMatch != NULL ) ? "Yes" : "No" );
  1154. EntityText(text_offset,tempstr,0);
  1155. text_offset++;
  1156. // state
  1157. Q_snprintf( tempstr, sizeof( tempstr ), "State: %d", m_iState.Get() );
  1158. EntityText(text_offset,tempstr,0);
  1159. text_offset++;
  1160. // recharge time
  1161. if ( gpGlobals->curtime < m_flRechargeTime )
  1162. {
  1163. float flPercent = ( m_flRechargeTime - gpGlobals->curtime ) / m_flCurrentRechargeDuration;
  1164. Q_snprintf( tempstr, sizeof( tempstr ), "Recharging: %.1f", flPercent );
  1165. EntityText(text_offset,tempstr,0);
  1166. text_offset++;
  1167. }
  1168. }
  1169. return text_offset;
  1170. }
  1171. //-----------------------------------------------------------------------------
  1172. // Purpose:
  1173. //-----------------------------------------------------------------------------
  1174. CObjectTeleporter* CObjectTeleporter::FindMatch( void )
  1175. {
  1176. int iObjType = GetType();
  1177. CObjectTeleporter *pMatch = NULL;
  1178. CTFPlayer *pBuilder = GetBuilder();
  1179. Assert( pBuilder || m_bWasMapPlaced );
  1180. if ( !pBuilder )
  1181. {
  1182. return NULL;
  1183. }
  1184. int i;
  1185. int iNumObjects = pBuilder->GetObjectCount();
  1186. for ( i=0; i<iNumObjects; i++ )
  1187. {
  1188. CBaseObject *pObj = pBuilder->GetObject(i);
  1189. if ( pObj && (pObj != this) && (iObjType == pObj->GetType()) )
  1190. {
  1191. CObjectTeleporter *pTele = dynamic_cast<CObjectTeleporter*>(pObj);
  1192. if ( pTele && (( IsEntrance() && pTele->IsExit() ) ||
  1193. ( IsExit() && pTele->IsEntrance() )) )
  1194. {
  1195. pMatch = pTele;
  1196. CObjectTeleporter* pOtherMatch = pMatch->GetMatchingTeleporter();
  1197. if ( pOtherMatch && pOtherMatch != this )
  1198. {
  1199. pMatch = NULL;
  1200. continue;
  1201. }
  1202. break;
  1203. }
  1204. }
  1205. }
  1206. if ( pMatch )
  1207. {
  1208. // Select the teleporter with the most upgrade
  1209. bool bFrom = (pMatch->GetUpgradeLevel() > GetUpgradeLevel() || pMatch->GetUpgradeMetal() > GetUpgradeMetal() );
  1210. CopyUpgradeStateToMatch( pMatch, bFrom );
  1211. }
  1212. return pMatch;
  1213. }
  1214. //-----------------------------------------------------------------------------
  1215. // Purpose:
  1216. //-----------------------------------------------------------------------------
  1217. void CObjectTeleporter::Explode( void )
  1218. {
  1219. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  1220. if ( pMatch )
  1221. {
  1222. pMatch->m_iHighestUpgradeLevel = 1;
  1223. pMatch->m_iUpgradeLevel = 1;
  1224. pMatch->m_iUpgradeMetal = 0;
  1225. int iHealth = pMatch->GetMaxHealthForCurrentLevel();
  1226. pMatch->UpdateMaxHealth( iHealth, true );
  1227. if ( pMatch->GetTeleportingPlayer() )
  1228. {
  1229. pMatch->GetTeleportingPlayer()->m_Shared.RemoveCond( TF_COND_SELECTED_TO_TELEPORT );
  1230. }
  1231. pMatch->SetTeleportingPlayer( NULL );
  1232. }
  1233. if ( m_hTeleportingPlayer.Get() )
  1234. {
  1235. m_hTeleportingPlayer.Get()->m_Shared.RemoveCond( TF_COND_SELECTED_TO_TELEPORT );
  1236. }
  1237. SetTeleportingPlayer( NULL );
  1238. BaseClass::Explode();
  1239. }
  1240. //-----------------------------------------------------------------------------
  1241. // Purpose: Update the max health value and scale the health value to match
  1242. //-----------------------------------------------------------------------------
  1243. void CObjectTeleporter::UpdateMaxHealth( int nHealth, bool bForce /* = false */ )
  1244. {
  1245. if ( m_bCarryDeploy && !bForce )
  1246. return;
  1247. float flPercentageHealth = (float)GetHealth()/(float)GetMaxHealth();
  1248. SetMaxHealth( nHealth );
  1249. SetHealth( nHealth * flPercentageHealth );
  1250. }
  1251. //-----------------------------------------------------------------------------
  1252. // Purpose: Raises the Teleporter one level
  1253. //-----------------------------------------------------------------------------
  1254. void CObjectTeleporter::StartUpgrading( void )
  1255. {
  1256. // Call our base class upgrading first to update our health and maxhealth
  1257. BaseClass::StartUpgrading();
  1258. // Tell our partner to match his maxhealth to ours
  1259. CObjectTeleporter *pMatch = GetMatchingTeleporter();
  1260. if ( pMatch && !m_bCarryDeploy && !pMatch->m_bCarryDeploy )
  1261. {
  1262. pMatch->UpdateMaxHealth( GetMaxHealth() );
  1263. }
  1264. SetState( TELEPORTER_STATE_UPGRADING );
  1265. }
  1266. void CObjectTeleporter::FinishUpgrading( void )
  1267. {
  1268. SetState( TELEPORTER_STATE_IDLE );
  1269. if ( ShouldQuickBuild() )
  1270. {
  1271. // See if we have a lower level match and upgrade them
  1272. if ( m_hMatchingTeleporter.Get() && m_hMatchingTeleporter->GetUpgradeLevel() < GetUpgradeLevel() )
  1273. {
  1274. CopyUpgradeStateToMatch( m_hMatchingTeleporter, false );
  1275. }
  1276. }
  1277. BaseClass::FinishUpgrading();
  1278. }
  1279. //-----------------------------------------------------------------------------
  1280. // Purpose:
  1281. //-----------------------------------------------------------------------------
  1282. bool CObjectTeleporter::InputWrenchHit( CTFPlayer *pPlayer, CTFWrench *pWrench, Vector hitLoc )
  1283. {
  1284. return BaseClass::InputWrenchHit( pPlayer, pWrench, hitLoc );
  1285. }
  1286. //-----------------------------------------------------------------------------
  1287. //
  1288. //-----------------------------------------------------------------------------
  1289. void CObjectTeleporter::MakeCarriedObject( CTFPlayer *pCarrier )
  1290. {
  1291. ShowDirectionArrow( false );
  1292. BaseClass::MakeCarriedObject( pCarrier );
  1293. }
  1294. //-----------------------------------------------------------------------------
  1295. // Purpose:
  1296. //-----------------------------------------------------------------------------
  1297. void CObjectTeleporter::InputEnable( inputdata_t &inputdata )
  1298. {
  1299. BaseClass::InputEnable( inputdata );
  1300. if ( !IsDisabled() )
  1301. {
  1302. if ( m_hMatchingTeleporter && m_hMatchingTeleporter->IsDisabled() )
  1303. {
  1304. m_hMatchingTeleporter->UpdateDisabledState();
  1305. if ( !m_hMatchingTeleporter->IsDisabled() )
  1306. {
  1307. m_hMatchingTeleporter->OnGoActive();
  1308. }
  1309. }
  1310. }
  1311. }
  1312. //-----------------------------------------------------------------------------
  1313. // Purpose:
  1314. //-----------------------------------------------------------------------------
  1315. void CObjectTeleporter::InputDisable( inputdata_t &inputdata )
  1316. {
  1317. BaseClass::InputDisable( inputdata );
  1318. if ( m_hMatchingTeleporter && !m_hMatchingTeleporter->IsDisabled() )
  1319. {
  1320. m_hMatchingTeleporter->SetDisabled( true );
  1321. m_hMatchingTeleporter->OnGoInactive();
  1322. }
  1323. }
  1324. void CObjectTeleporter::SpawnBread( const CTFPlayer* pTeleportingPlayer )
  1325. {
  1326. if( !pTeleportingPlayer )
  1327. return;
  1328. const char* pszModelName = g_pszBreadModels[ RandomInt( 0, TF_LAST_NORMAL_CLASS - TF_FIRST_NORMAL_CLASS - 1 ) ];
  1329. CPhysicsProp *pProp = NULL;
  1330. MDLHandle_t h = mdlcache->FindMDL( pszModelName );
  1331. if ( h != MDLHANDLE_INVALID )
  1332. {
  1333. // Must have vphysics to place as a physics prop
  1334. studiohdr_t *pStudioHdr = mdlcache->GetStudioHdr( h );
  1335. if ( pStudioHdr && mdlcache->GetVCollide( h ) )
  1336. {
  1337. // Try to create entity
  1338. pProp = dynamic_cast< CPhysicsProp * >( CreateEntityByName( "prop_physics_override" ) );
  1339. if ( pProp )
  1340. {
  1341. Vector vecSpawn = GetAbsOrigin();
  1342. vecSpawn.z += TELEPORTER_MAXS.z + 50;
  1343. QAngle qSpawnAngles = GetAbsAngles();
  1344. pProp->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  1345. // so it can be pushed by airblast
  1346. pProp->AddFlag( FL_GRENADE );
  1347. // so that it will always be interactable with the player
  1348. char buf[512];
  1349. // Pass in standard key values
  1350. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", vecSpawn.x, vecSpawn.y, vecSpawn.z );
  1351. pProp->KeyValue( "origin", buf );
  1352. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", qSpawnAngles.x, qSpawnAngles.y, qSpawnAngles.z );
  1353. pProp->KeyValue( "angles", buf );
  1354. pProp->KeyValue( "model", pszModelName );
  1355. pProp->KeyValue( "fademindist", "-1" );
  1356. pProp->KeyValue( "fademaxdist", "0" );
  1357. pProp->KeyValue( "fadescale", "1" );
  1358. pProp->KeyValue( "inertiaScale", "1.0" );
  1359. pProp->KeyValue( "physdamagescale", "0.1" );
  1360. pProp->Precache();
  1361. DispatchSpawn( pProp );
  1362. pProp->m_takedamage = DAMAGE_YES; // Take damage, otherwise this can block trains
  1363. pProp->SetHealth( 5000 );
  1364. pProp->Activate();
  1365. IPhysicsObject *pPhysicsObj = pProp->VPhysicsGetObject();
  1366. if ( pPhysicsObj )
  1367. {
  1368. AngularImpulse angImpulse( RandomFloat( -100, 100 ), RandomFloat( -100, 100 ), RandomFloat( -100, 100 ) );
  1369. Vector vForward;
  1370. AngleVectors( qSpawnAngles, &vForward );
  1371. Vector vecVel = ( vForward * 100 ) + Vector( 0, 0, 200 ) + RandomVector( -50, 50 );
  1372. pPhysicsObj->SetVelocityInstantaneous( &vecVel, &angImpulse );
  1373. }
  1374. // Die in 10 seconds
  1375. pProp->ThinkSet( &CBaseEntity::SUB_Remove, gpGlobals->curtime + 10, "DieContext" );
  1376. }
  1377. }
  1378. mdlcache->Release( h ); // counterbalance addref from within FindMDL
  1379. }
  1380. }
  1381. void CObjectTeleporter::FireGameEvent( IGameEvent *event )
  1382. {
  1383. if ( FStrEq( event->GetName(), "player_spawn" ) ||
  1384. FStrEq( event->GetName(), "player_team" ) )
  1385. {
  1386. // On instant-spawn servers, players can change teams just as the teleporter
  1387. // queues them for a teleport and will still teleport them even if they respawn / change team.
  1388. //
  1389. // If we hear a spawn or team-change event for our queued player, clear them from the queue
  1390. if ( !m_hTeleportingPlayer.Get() )
  1391. return;
  1392. const int iUserID = event->GetInt( "userid" );
  1393. if ( iUserID == m_hTeleportingPlayer->GetUserID() )
  1394. {
  1395. SetTeleportingPlayer( NULL );
  1396. }
  1397. }
  1398. }