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.

355 lines
8.8 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. // c_fish.cpp
  9. // Simple fish client-side logic
  10. // Author: Michael S. Booth, April 2005
  11. #include "cbase.h"
  12. #include <bitbuf.h>
  13. #include "engine/ivdebugoverlay.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz );
  17. ConVar FishDebug( "fish_debug", "0", FCVAR_CHEAT, "Show debug info for fish" );
  18. //-----------------------------------------------------------------------------
  19. /**
  20. * Client-side fish entity
  21. */
  22. class C_Fish : public C_BaseAnimating
  23. {
  24. public:
  25. DECLARE_CLASS( C_Fish, C_BaseAnimating );
  26. DECLARE_CLIENTCLASS();
  27. virtual void Spawn( void );
  28. virtual void ClientThink();
  29. virtual void OnDataChanged( DataUpdateType_t type );
  30. private:
  31. friend void RecvProxy_FishOriginX( const CRecvProxyData *pData, void *pStruct, void *pOut );
  32. friend void RecvProxy_FishOriginY( const CRecvProxyData *pData, void *pStruct, void *pOut );
  33. Vector m_pos; ///< local position
  34. Vector m_vel; ///< local velocity
  35. QAngle m_angles; ///< local angles
  36. int m_localLifeState; ///< our version of m_lifeState
  37. float m_deathDepth; ///< water depth when we died
  38. float m_deathAngle; ///< angle to float at when dead
  39. float m_buoyancy; ///< so each fish floats at a different rate when dead
  40. CountdownTimer m_wiggleTimer; ///< for simulating swimming motions
  41. float m_wigglePhase; ///< where in the wiggle sinusoid we are
  42. float m_wiggleRate; ///< the speed of our wiggling
  43. Vector m_actualPos; ///< position from server
  44. QAngle m_actualAngles; ///< angles from server
  45. Vector m_poolOrigin;
  46. float m_waterLevel; ///< Z coordinate of water surface
  47. bool m_gotUpdate; ///< true after we have received a network update
  48. enum { MAX_ERROR_HISTORY = 20 };
  49. float m_errorHistory[ MAX_ERROR_HISTORY ]; ///< error history samples
  50. int m_errorHistoryIndex;
  51. int m_errorHistoryCount;
  52. float m_averageError;
  53. };
  54. //-----------------------------------------------------------------------------
  55. void RecvProxy_FishOriginX( const CRecvProxyData *pData, void *pStruct, void *pOut )
  56. {
  57. C_Fish *fish = (C_Fish *)pStruct;
  58. float *out = (float *)pOut;
  59. *out = pData->m_Value.m_Float + fish->m_poolOrigin.x;
  60. }
  61. void RecvProxy_FishOriginY( const CRecvProxyData *pData, void *pStruct, void *pOut )
  62. {
  63. C_Fish *fish = (C_Fish *)pStruct;
  64. float *out = (float *)pOut;
  65. *out = pData->m_Value.m_Float + fish->m_poolOrigin.y;
  66. }
  67. IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_Fish, DT_CFish, CFish )
  68. RecvPropVector( RECVINFO(m_poolOrigin) ),
  69. RecvPropFloat( RECVINFO_NAME( m_actualPos.x, m_x ), 0, RecvProxy_FishOriginX ),
  70. RecvPropFloat( RECVINFO_NAME( m_actualPos.y, m_y ), 0, RecvProxy_FishOriginY ),
  71. RecvPropFloat( RECVINFO_NAME( m_actualPos.z, m_z ) ),
  72. RecvPropFloat( RECVINFO_NAME( m_actualAngles.y, m_angle ) ),
  73. RecvPropInt( RECVINFO(m_nModelIndex) ),
  74. RecvPropInt( RECVINFO(m_lifeState) ),
  75. RecvPropFloat( RECVINFO(m_waterLevel) ), ///< get this from the server in case we die when slightly out of the water due to error correction
  76. END_RECV_TABLE()
  77. //-----------------------------------------------------------------------------
  78. void C_Fish::Spawn( void )
  79. {
  80. BaseClass::Spawn();
  81. m_angles = QAngle( 0, 0, 0 );
  82. m_actualAngles = m_angles;
  83. m_vel = Vector( 0, 0, 0 );
  84. m_gotUpdate = false;
  85. m_localLifeState = LIFE_ALIVE;
  86. m_buoyancy = RandomFloat( 0.4f, 1.0f );
  87. m_errorHistoryIndex = 0;
  88. m_errorHistoryCount = 0;
  89. m_averageError = 0.0f;
  90. SetNextClientThink( CLIENT_THINK_ALWAYS );
  91. }
  92. //-----------------------------------------------------------------------------
  93. void C_Fish::ClientThink()
  94. {
  95. if (FishDebug.GetBool())
  96. {
  97. if ( debugoverlay )
  98. {
  99. debugoverlay->AddLineOverlay( m_pos, m_actualPos, 255, 0, 0, true, 0.1f );
  100. switch( m_localLifeState )
  101. {
  102. case LIFE_DYING:
  103. debugoverlay->AddTextOverlay( m_pos, 0.1f, "DYING" );
  104. break;
  105. case LIFE_DEAD:
  106. debugoverlay->AddTextOverlay( m_pos, 0.1f, "DEAD" );
  107. break;
  108. }
  109. }
  110. }
  111. float deltaT = gpGlobals->frametime;
  112. // check if we just died
  113. if (m_localLifeState == LIFE_ALIVE && m_lifeState != LIFE_ALIVE)
  114. {
  115. // we have died
  116. m_localLifeState = LIFE_DYING;
  117. m_deathDepth = m_pos.z;
  118. // determine surface float angle
  119. m_deathAngle = RandomFloat( 87.0f, 93.0f ) * ((RandomInt( 0, 100 ) < 50) ? 1.0f : -1.0f);
  120. }
  121. switch( m_localLifeState )
  122. {
  123. case LIFE_DYING:
  124. {
  125. // depth parameter
  126. float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth);
  127. t *= t;
  128. // roll onto side
  129. m_angles.z = m_deathAngle * t;
  130. // float to surface
  131. const float fudge = 2.0f;
  132. if (m_pos.z < m_waterLevel - fudge)
  133. {
  134. m_vel.z += (1.0f - t) * m_buoyancy * deltaT;
  135. }
  136. else
  137. {
  138. m_localLifeState = LIFE_DEAD;
  139. }
  140. break;
  141. }
  142. case LIFE_DEAD:
  143. {
  144. // depth parameter
  145. float t = (m_pos.z - m_deathDepth) / (m_waterLevel - m_deathDepth);
  146. t *= t;
  147. // roll onto side
  148. m_angles.z = m_deathAngle * t;
  149. // keep near water surface
  150. const float sub = 0.5f;
  151. m_vel.z += 10.0f * (m_waterLevel - m_pos.z - sub) * deltaT;
  152. // bob on surface
  153. const float rollAmp = 5.0f;
  154. const float rollFreq = 2.33f;
  155. m_angles.z += rollAmp * sin( rollFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
  156. const float rollAmp2 = 7.0f;
  157. const float rollFreq2 = 4.0f;
  158. m_angles.x += rollAmp2 * sin( rollFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
  159. const float bobAmp = 0.75f;
  160. const float bobFreq = 4.0f;
  161. m_vel.z += bobAmp * sin( bobFreq * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
  162. const float bobAmp2 = 0.75f;
  163. const float bobFreq2 = 3.333f;
  164. m_vel.z += bobAmp2 * sin( bobFreq2 * (gpGlobals->curtime + 10.0f * entindex()) ) * deltaT;
  165. // decay movement speed to zero
  166. const float drag = 1.0f;
  167. m_vel.z -= drag * m_vel.z * deltaT;
  168. break;
  169. }
  170. case LIFE_ALIVE:
  171. {
  172. // use server-side Z coordinate directly
  173. m_pos.z = m_actualPos.z;
  174. // use server-side angles
  175. m_angles = m_actualAngles;
  176. // fishy wiggle based on movement
  177. if (!m_wiggleTimer.IsElapsed())
  178. {
  179. float swimPower = 1.0f - (m_wiggleTimer.GetElapsedTime() / m_wiggleTimer.GetCountdownDuration());
  180. const float amp = 6.0f * swimPower;
  181. float wiggle = amp * sin( m_wigglePhase );
  182. m_wigglePhase += m_wiggleRate * deltaT;
  183. // wiggle decay
  184. const float wiggleDecay = 5.0f;
  185. m_wiggleRate -= wiggleDecay * deltaT;
  186. m_angles.y += wiggle;
  187. }
  188. break;
  189. }
  190. }
  191. // compute error between our local position and actual server position
  192. Vector error = m_actualPos - m_pos;
  193. error.z = 0.0f;
  194. float errorLen = error.Length();
  195. if (m_localLifeState == LIFE_ALIVE)
  196. {
  197. // if error is far above average, start swimming
  198. const float wiggleThreshold = 2.0f;
  199. if (errorLen - m_averageError > wiggleThreshold)
  200. {
  201. // if error is large, we must have started swimming
  202. const float swimTime = 5.0f;
  203. m_wiggleTimer.Start( swimTime );
  204. m_wiggleRate = 2.0f * errorLen;
  205. const float maxWiggleRate = 30.0f;
  206. if (m_wiggleRate > maxWiggleRate)
  207. {
  208. m_wiggleRate = maxWiggleRate;
  209. }
  210. }
  211. // update average error
  212. m_errorHistory[ m_errorHistoryIndex++ ] = errorLen;
  213. if (m_errorHistoryIndex >= MAX_ERROR_HISTORY)
  214. {
  215. m_errorHistoryIndex = 0;
  216. m_errorHistoryCount = MAX_ERROR_HISTORY;
  217. }
  218. else if (m_errorHistoryCount < MAX_ERROR_HISTORY)
  219. {
  220. ++m_errorHistoryCount;
  221. }
  222. m_averageError = 0.0f;
  223. if (m_errorHistoryCount)
  224. {
  225. for( int r=0; r<m_errorHistoryCount; ++r )
  226. {
  227. m_averageError += m_errorHistory[r];
  228. }
  229. m_averageError /= (float)m_errorHistoryCount;
  230. }
  231. }
  232. // keep fish motion smooth by correcting towards actual server position
  233. // NOTE: This only tracks XY motion
  234. const float maxError = 20.0f;
  235. float errorT = errorLen / maxError;
  236. if (errorT > 1.0f)
  237. {
  238. errorT = 1.0f;
  239. }
  240. // we want a nonlinear spring force for tracking
  241. errorT *= errorT;
  242. // as fish move faster, their error increases - use a stiffer spring when fast, and a weak one when slow
  243. const float trackRate = 0.0f + errorT * 115.0f;
  244. m_vel.x += trackRate * error.x * deltaT;
  245. m_vel.y += trackRate * error.y * deltaT;
  246. const float trackDrag = 2.0f + errorT * 6.0f;
  247. m_vel.x -= trackDrag * m_vel.x * deltaT;
  248. m_vel.y -= trackDrag * m_vel.y * deltaT;
  249. // euler integration
  250. m_pos += m_vel * deltaT;
  251. SetNetworkOrigin( m_pos );
  252. SetAbsOrigin( m_pos );
  253. SetNetworkAngles( m_angles );
  254. SetAbsAngles( m_angles );
  255. }
  256. //-----------------------------------------------------------------------------
  257. void C_Fish::OnDataChanged( DataUpdateType_t type )
  258. {
  259. //if (!m_gotUpdate)
  260. if (type == DATA_UPDATE_CREATED)
  261. {
  262. // initial update
  263. m_gotUpdate = true;
  264. m_pos = m_actualPos;
  265. m_vel = Vector( 0, 0, 0 );
  266. return;
  267. }
  268. }