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.

342 lines
11 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Entity which teleports touched entities and reorients their physics
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "baseentity.h"
  8. #include "triggers.h"
  9. #include "modelentities.h"
  10. #include "saverestore_utlvector.h"
  11. #include "player_pickup.h"
  12. #include "vphysics/friction.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. #define TRIGGER_DISABLED_THINK "PortalDisabledThink"
  16. ConVar portal_debug( "portal_debug", "0", FCVAR_CHEAT, "Turn on debugging for portal connections." );
  17. //////////////////////////////////////////////////////////////////////////
  18. // CTriggerPortal
  19. // Moves touched entity to a target location, changing the model's orientation
  20. // to match the exit target. It differs from CTriggerTeleport in that it
  21. // reorients physics and has inputs to enable/disable its function.
  22. //////////////////////////////////////////////////////////////////////////
  23. class CTriggerPortal : public CBaseTrigger
  24. {
  25. public:
  26. DECLARE_DATADESC();
  27. DECLARE_CLASS( CTriggerPortal, CBaseTrigger );
  28. DECLARE_SERVERCLASS();
  29. virtual void Spawn( void );
  30. virtual void Activate();
  31. void Touch( CBaseEntity *pOther );
  32. void EndTouch(CBaseEntity *pOther);
  33. void DisableForIncomingEntity( CBaseEntity *pEntity );
  34. bool IsTouchingPortal( CBaseEntity *pEntity );
  35. void DisabledThink( void );
  36. // TEMP: Since brushes have no directionality, give this wall a forward face specified in hammer
  37. QAngle m_qFaceAngles;
  38. private:
  39. string_t m_strRemotePortal;
  40. CNetworkHandle( CTriggerPortal, m_hRemotePortal );
  41. CUtlVector<EHANDLE> m_hDisabledForEntities;
  42. // Input for setting remote portal entity (for teleporting to it)
  43. void SetRemotePortal ( const char* strRemotePortalName );
  44. void InputSetRemotePortal ( inputdata_t &inputdata );
  45. };
  46. LINK_ENTITY_TO_CLASS( trigger_portal, CTriggerPortal );
  47. BEGIN_DATADESC( CTriggerPortal )
  48. DEFINE_KEYFIELD( m_strRemotePortal, FIELD_STRING, "RemotePortal" ),
  49. DEFINE_FIELD( m_hRemotePortal, FIELD_EHANDLE ),
  50. DEFINE_UTLVECTOR( m_hDisabledForEntities, FIELD_EHANDLE ),
  51. // TEMP: Only keep this field while portals are still brushes
  52. DEFINE_FIELD( m_qFaceAngles, FIELD_VECTOR ),
  53. DEFINE_THINKFUNC( DisabledThink ),
  54. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  55. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  56. DEFINE_INPUTFUNC( FIELD_STRING, "SetRemotePortal", InputSetRemotePortal ),
  57. END_DATADESC()
  58. IMPLEMENT_SERVERCLASS_ST( CTriggerPortal, DT_TriggerPortal )
  59. SendPropEHandle(SENDINFO(m_hRemotePortal)),
  60. END_SEND_TABLE()
  61. //-----------------------------------------------------------------------------
  62. // Purpose:
  63. //-----------------------------------------------------------------------------
  64. void CTriggerPortal::Spawn( void )
  65. {
  66. BaseClass::Spawn();
  67. InitTrigger();
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Purpose:
  71. // Input : -
  72. //-----------------------------------------------------------------------------
  73. void CTriggerPortal::Activate()
  74. {
  75. BaseClass::Activate();
  76. m_qFaceAngles = this->GetAbsAngles();
  77. // keep the remote portal's pointer at activate time to avoid redundant FindEntity calls
  78. if ( m_strRemotePortal != NULL_STRING )
  79. {
  80. SetRemotePortal( STRING(m_strRemotePortal) );
  81. m_strRemotePortal = NULL_STRING;
  82. }
  83. }
  84. //-----------------------------------------------------------------------------
  85. // Purpose:
  86. // Input : &inputdata -
  87. //-----------------------------------------------------------------------------
  88. void CTriggerPortal::InputSetRemotePortal(inputdata_t &inputdata )
  89. {
  90. SetRemotePortal( inputdata.value.String() );
  91. }
  92. //-----------------------------------------------------------------------------
  93. // Purpose:
  94. // Input : strRemotePortalName -
  95. //-----------------------------------------------------------------------------
  96. void CTriggerPortal::SetRemotePortal(const char *strRemotePortalName )
  97. {
  98. m_hRemotePortal = dynamic_cast<CTriggerPortal*> (gEntList.FindEntityByName( NULL, strRemotePortalName, NULL, NULL, NULL ));
  99. if ( m_hRemotePortal == NULL )
  100. {
  101. Warning ( "trigger_portal: Cannot find remote portal entity named %s\n", strRemotePortalName );
  102. }
  103. }
  104. //-----------------------------------------------------------------------------
  105. // Purpose:
  106. // Input : *pOther -
  107. //-----------------------------------------------------------------------------
  108. void CTriggerPortal::EndTouch(CBaseEntity *pOther)
  109. {
  110. BaseClass::EndTouch(pOther);
  111. if ( portal_debug.GetBool() )
  112. {
  113. Msg("%s ENDTOUCH: for %s\n", GetDebugName(), pOther->GetDebugName() );
  114. }
  115. EHANDLE hHandle;
  116. hHandle = pOther;
  117. m_hDisabledForEntities.FindAndRemove( hHandle );
  118. }
  119. //-----------------------------------------------------------------------------
  120. // Purpose: Upon touching a non-filtered entity, CTriggerPortal teleports them to it's
  121. // remote portal location.
  122. // Input : *pOther -
  123. //-----------------------------------------------------------------------------
  124. void CTriggerPortal::Touch( CBaseEntity *pOther )
  125. {
  126. // If we are enabled, and allowed to react to the touched entity
  127. if ( PassesTriggerFilters(pOther) )
  128. {
  129. // If we somehow lost our pointer to the remote portal, get a new one
  130. if ( m_hRemotePortal == NULL )
  131. {
  132. Disable();
  133. return;
  134. }
  135. bool bDebug = portal_debug.GetBool();
  136. if ( bDebug )
  137. {
  138. Msg("%s TOUCH: for %s\n", GetDebugName(), pOther->GetDebugName() );
  139. }
  140. // Don't touch entities that came through us and haven't left us yet.
  141. EHANDLE hHandle;
  142. hHandle = pOther;
  143. if ( m_hDisabledForEntities.Find(hHandle) != m_hDisabledForEntities.InvalidIndex() )
  144. {
  145. Msg(" IGNORED\n", GetDebugName(), pOther->GetDebugName() );
  146. return;
  147. }
  148. Pickup_ForcePlayerToDropThisObject( pOther );
  149. // de-ground this entity
  150. pOther->SetGroundEntity( NULL );
  151. // Build a this --> remote transformation
  152. VMatrix matMyModelToWorld, matMyInverse;
  153. matMyModelToWorld = this->EntityToWorldTransform();
  154. MatrixInverseGeneral ( matMyModelToWorld, matMyInverse );
  155. // Teleport our object
  156. VMatrix matRemotePortalTransform = m_hRemotePortal->EntityToWorldTransform();
  157. Vector ptNewOrigin, vLook, vRight, vUp, vNewLook;
  158. pOther->GetVectors( &vLook, &vRight, &vUp );
  159. // Move origin
  160. ptNewOrigin = matMyInverse * pOther->GetAbsOrigin();
  161. ptNewOrigin = matRemotePortalTransform * Vector( ptNewOrigin.x, -ptNewOrigin.y, ptNewOrigin.z );
  162. // Re-aim camera
  163. vNewLook = matMyInverse.ApplyRotation( vLook );
  164. vNewLook = matRemotePortalTransform.ApplyRotation( Vector( -vNewLook.x, -vNewLook.y, vNewLook.z ) );
  165. // Reorient the physics
  166. Vector vVelocity, vOldVelocity;
  167. pOther->GetVelocity( &vOldVelocity );
  168. vVelocity = matMyInverse.ApplyRotation( vOldVelocity );
  169. vVelocity = matRemotePortalTransform.ApplyRotation( Vector( -vVelocity.x, -vVelocity.y, vVelocity.z ) );
  170. QAngle qNewAngles;
  171. VectorAngles( vNewLook, qNewAngles );
  172. if ( pOther->IsPlayer() )
  173. {
  174. ((CBasePlayer*)pOther)->SnapEyeAngles(qNewAngles);
  175. }
  176. Vector vecOldPos = pOther->WorldSpaceCenter();
  177. if ( bDebug )
  178. {
  179. NDebugOverlay::Box( pOther->GetAbsOrigin(), pOther->WorldAlignMins(), pOther->WorldAlignMaxs(), 255,0,0, 8, 20 );
  180. NDebugOverlay::Axis( pOther->GetAbsOrigin(), pOther->GetAbsAngles(), 10.0f, true, 50 );
  181. }
  182. // place player at the new destination
  183. CTriggerPortal *pPortal = m_hRemotePortal.Get();
  184. pPortal->DisableForIncomingEntity( pOther );
  185. pOther->Teleport( &ptNewOrigin, &qNewAngles, &vVelocity );
  186. if ( bDebug )
  187. {
  188. NDebugOverlay::Box( pOther->GetAbsOrigin(), pOther->WorldAlignMins(), pOther->WorldAlignMaxs(), 0,255,0, 8, 20 );
  189. NDebugOverlay::Line( vecOldPos, pOther->WorldSpaceCenter(), 0,255,0, true, 20 );
  190. NDebugOverlay::Axis( pOther->GetAbsOrigin(), pOther->GetAbsAngles(), 10.0f, true, 50 );
  191. Msg("%s TELEPORTED: %s\n", GetDebugName(), pOther->GetDebugName() );
  192. }
  193. // test collision on the new teleport location
  194. Vector vMin, vMax, vCenter;
  195. pOther->CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
  196. vCenter = (vMin + vMax) * 0.5f;
  197. vMin -= vCenter;
  198. vMax -= vCenter;
  199. Vector vStart, vEnd;
  200. vStart = ptNewOrigin;
  201. vEnd = ptNewOrigin;
  202. Ray_t ray;
  203. ray.Init( vStart, vEnd, vMin, vMax );
  204. trace_t tr;
  205. pPortal->TestCollision( ray, pOther->PhysicsSolidMaskForEntity(), tr );
  206. // Teleportation caused us to hit something, deal with it.
  207. if ( tr.DidHit() )
  208. {
  209. }
  210. }
  211. }
  212. //-----------------------------------------------------------------------------
  213. // Purpose:
  214. //-----------------------------------------------------------------------------
  215. void CTriggerPortal::DisableForIncomingEntity( CBaseEntity *pEntity )
  216. {
  217. EHANDLE hHandle;
  218. hHandle = pEntity;
  219. Assert( m_hDisabledForEntities.Find(hHandle) == m_hDisabledForEntities.InvalidIndex() );
  220. m_hDisabledForEntities.AddToTail( hHandle );
  221. // Start thinking, and remove the other as soon as it's not touching me.
  222. // Needs to be done in addition to EndTouch, because entities may move fast
  223. // enough through the portal to come out not touching the other portal.
  224. SetContextThink( DisabledThink, gpGlobals->curtime + 0.1, TRIGGER_DISABLED_THINK );
  225. }
  226. //-----------------------------------------------------------------------------
  227. // Purpose:
  228. //-----------------------------------------------------------------------------
  229. void CTriggerPortal::DisabledThink( void )
  230. {
  231. // If we've got no disabled entities left, we're done
  232. if ( !m_hDisabledForEntities.Count() )
  233. {
  234. SetContextThink( NULL, gpGlobals->curtime, TRIGGER_DISABLED_THINK );
  235. return;
  236. }
  237. for ( int i = m_hDisabledForEntities.Count()-1; i >= 0; i-- )
  238. {
  239. CBaseEntity *pEntity = m_hDisabledForEntities[i];
  240. if ( !pEntity || !IsTouchingPortal(pEntity) )
  241. {
  242. m_hDisabledForEntities.Remove(i);
  243. }
  244. }
  245. SetContextThink( DisabledThink, gpGlobals->curtime + 0.1, TRIGGER_DISABLED_THINK );
  246. }
  247. //-----------------------------------------------------------------------------
  248. // Purpose:
  249. //-----------------------------------------------------------------------------
  250. bool CTriggerPortal::IsTouchingPortal( CBaseEntity *pEntity )
  251. {
  252. // First, check the touchlinks. This will find non-vphysics entities touching us
  253. touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
  254. if ( root )
  255. {
  256. for ( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
  257. {
  258. CBaseEntity *pTouch = link->entityTouched;
  259. if ( pTouch == pEntity )
  260. return true;
  261. }
  262. }
  263. // Then check the friction snapshot. This will find vphysics objects touching us.
  264. IPhysicsObject *pPhysics = VPhysicsGetObject();
  265. if ( !pPhysics )
  266. return false;
  267. IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
  268. bool bFound = false;
  269. while ( pSnapshot->IsValid() )
  270. {
  271. IPhysicsObject *pOther = pSnapshot->GetObject( 1 );
  272. if ( ((CBaseEntity *)pOther->GetGameData()) == pEntity )
  273. {
  274. bFound = true;
  275. break;
  276. }
  277. pSnapshot->NextFrictionData();
  278. }
  279. pPhysics->DestroyFrictionSnapshot( pSnapshot );
  280. return bFound;
  281. }