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.

733 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //===========================================================================//
  8. // fish.cpp
  9. // Simple fish behavior
  10. // Author: Michael S. Booth, April 2005
  11. #include "cbase.h"
  12. #include "fish.h"
  13. #include "saverestore_utlvector.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. ConVar fish_dormant( "fish_dormant", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Turns off interactive fish behavior. Fish become immobile and unresponsive." );
  17. //-----------------------------------------------------------------------------------------------------
  18. LINK_ENTITY_TO_CLASS( fish, CFish );
  19. //-----------------------------------------------------------------------------------------------------
  20. BEGIN_DATADESC( CFish )
  21. DEFINE_FIELD( m_pool, FIELD_EHANDLE ),
  22. DEFINE_FIELD( m_id, FIELD_INTEGER ),
  23. DEFINE_FIELD( m_angle, FIELD_FLOAT ),
  24. DEFINE_FIELD( m_angleChange, FIELD_FLOAT ),
  25. DEFINE_FIELD( m_forward, FIELD_VECTOR ),
  26. DEFINE_FIELD( m_perp, FIELD_VECTOR ),
  27. DEFINE_FIELD( m_poolOrigin, FIELD_POSITION_VECTOR ),
  28. DEFINE_FIELD( m_waterLevel, FIELD_FLOAT ),
  29. DEFINE_FIELD( m_speed, FIELD_FLOAT ),
  30. DEFINE_FIELD( m_desiredSpeed, FIELD_FLOAT ),
  31. DEFINE_FIELD( m_calmSpeed, FIELD_FLOAT ),
  32. DEFINE_FIELD( m_panicSpeed, FIELD_FLOAT ),
  33. DEFINE_FIELD( m_avoidRange, FIELD_FLOAT ),
  34. DEFINE_FIELD( m_turnClockwise, FIELD_BOOLEAN ),
  35. END_DATADESC()
  36. //-----------------------------------------------------------------------------------------------------
  37. /**
  38. * Send fish position relative to pool origin
  39. */
  40. void SendProxy_FishOriginX( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
  41. {
  42. CFish *fish = (CFish *)pStruct;
  43. Assert( fish );
  44. const Vector &v = fish->GetAbsOrigin();
  45. Vector origin = fish->m_poolOrigin;
  46. pOut->m_Float = v.x - origin.x;
  47. }
  48. void SendProxy_FishOriginY( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
  49. {
  50. CFish *fish = (CFish *)pStruct;
  51. Assert( fish );
  52. const Vector &v = fish->GetAbsOrigin();
  53. Vector origin = fish->m_poolOrigin;
  54. pOut->m_Float = v.y - origin.y;
  55. }
  56. // keep angle in normalized range when sending it
  57. void SendProxy_FishAngle( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
  58. {
  59. float value = *((float *)pData);
  60. while( value > 360.0f )
  61. value -= 360.0f;
  62. while (value < 0.0f)
  63. value += 360.0f;
  64. pOut->m_Float = value;
  65. }
  66. /**
  67. * NOTE: Do NOT use SPROP_CHANGES_OFTEN, as it will reorder this list.
  68. * The pool origin must arrive befoore m_x and m_y or the fish will
  69. * respawn at the origin and zip back to their proper places.
  70. */
  71. IMPLEMENT_SERVERCLASS_ST_NOBASE( CFish, DT_CFish )
  72. SendPropVector( SENDINFO(m_poolOrigin), -1, SPROP_COORD, 0.0f, HIGH_DEFAULT ), // only sent once
  73. SendPropFloat( SENDINFO(m_angle), 7, 0 /*SPROP_CHANGES_OFTEN*/, 0.0f, 360.0f, SendProxy_FishAngle ),
  74. SendPropFloat( SENDINFO(m_x), 7, 0 /*SPROP_CHANGES_OFTEN*/, -255.0f, 255.0f ),
  75. SendPropFloat( SENDINFO(m_y), 7, 0 /*SPROP_CHANGES_OFTEN*/, -255.0f, 255.0f ),
  76. SendPropFloat( SENDINFO(m_z), -1, SPROP_COORD ), // only sent once
  77. SendPropModelIndex( SENDINFO(m_nModelIndex) ),
  78. SendPropInt( SENDINFO(m_lifeState) ),
  79. SendPropFloat( SENDINFO(m_waterLevel) ), // only sent once
  80. END_SEND_TABLE()
  81. //-------------------------------------------------------------------------------------------------------------
  82. CFish::CFish( void )
  83. {
  84. }
  85. //-------------------------------------------------------------------------------------------------------------
  86. CFish::~CFish()
  87. {
  88. }
  89. //-------------------------------------------------------------------------------------------------------------
  90. void CFish::Initialize( CFishPool *pool, unsigned int id )
  91. {
  92. m_pool = pool;
  93. m_id = id;
  94. m_poolOrigin = pool->GetAbsOrigin();
  95. m_waterLevel = pool->GetWaterLevel();
  96. // pass relative position to the client
  97. Vector deltaPos = GetAbsOrigin() - m_poolOrigin;
  98. m_x = deltaPos.x;
  99. m_y = deltaPos.y;
  100. m_z = m_poolOrigin->z;
  101. SetModel( pool->GetModelName().ToCStr() );
  102. }
  103. //-------------------------------------------------------------------------------------------------------------
  104. void CFish::Spawn( void )
  105. {
  106. Precache();
  107. SetSolid( SOLID_BBOX );
  108. AddSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_NOT_SOLID | FSOLID_TRIGGER );
  109. SetMoveType( MOVETYPE_FLY );
  110. m_angle = RandomFloat( 0.0f, 360.0f );
  111. m_angleChange = 0.0f;
  112. m_forward = Vector( 1.0f, 0.0, 0.0f );
  113. m_perp.x = -m_forward.y;
  114. m_perp.y = m_forward.x;
  115. m_perp.z = 0.0f;
  116. m_speed = 0.0f;
  117. m_calmSpeed = RandomFloat( 10.0f, 20.0f );
  118. m_panicSpeed = m_calmSpeed * RandomFloat( 4.0f, 5.0f );
  119. m_desiredSpeed = m_calmSpeed;
  120. m_turnClockwise = (RandomInt( 0, 100 ) < 50);
  121. m_avoidRange = RandomFloat( 40.0f, 75.0f );
  122. m_iHealth = 1;
  123. m_iMaxHealth = 1;
  124. m_takedamage = DAMAGE_YES;
  125. // spread out a bit
  126. m_disperseTimer.Start( RandomFloat( 0.0f, 10.0f ) );
  127. m_goTimer.Start( RandomFloat( 10.0f, 60.0f ) );
  128. m_moveTimer.Start( RandomFloat( 2.0f, 10.0 ) );
  129. m_desiredSpeed = m_calmSpeed;
  130. }
  131. //-------------------------------------------------------------------------------------------------------------
  132. void CFish::Event_Killed( const CTakeDamageInfo &info )
  133. {
  134. m_takedamage = DAMAGE_NO;
  135. m_lifeState = LIFE_DEAD;
  136. }
  137. //-------------------------------------------------------------------------------------------------------------
  138. /**
  139. * In contact with "other"
  140. */
  141. void CFish::Touch( CBaseEntity *other )
  142. {
  143. if (other && other->IsPlayer())
  144. {
  145. // touched a Player - panic!
  146. Panic();
  147. }
  148. }
  149. //-------------------------------------------------------------------------------------------------------------
  150. /**
  151. * Influence my motion to flock with other nearby fish
  152. * 'amount' ranges from zero to one, representing the amount of flocking influence allowed
  153. * If 'other' is NULL, flock to the center of the pool.
  154. */
  155. void CFish::FlockTo( CFish *other, float amount )
  156. {
  157. // allow fish to disperse a bit at round start
  158. if (!m_disperseTimer.IsElapsed())
  159. return;
  160. const float maxRange = (other) ? 100.0f : 300.0f;
  161. Vector to = (other) ? (other->GetAbsOrigin() - GetAbsOrigin()) : (m_pool->GetAbsOrigin() - GetAbsOrigin());
  162. float range = to.NormalizeInPlace();
  163. if (range > maxRange)
  164. return;
  165. // if they are close and we are moving together, avoid them
  166. const float avoidRange = 25.0f;
  167. if (other && range < avoidRange)
  168. {
  169. // compute their relative velocity to us
  170. Vector relVel = other->GetAbsVelocity() - GetAbsVelocity();
  171. if (DotProduct( to, relVel ) < 0.0f)
  172. {
  173. const float avoidPower = 5.0f;
  174. // their comin' right at us! - avoid
  175. if (DotProduct( m_perp, to ) > 0.0f)
  176. {
  177. m_angleChange -= avoidPower * (1.0f - range/avoidRange);
  178. }
  179. else
  180. {
  181. m_angleChange += avoidPower * (1.0f - range/avoidRange);
  182. }
  183. return;
  184. }
  185. }
  186. // turn is 2 if 'other' is behind us, 1 perpendicular, and 0 straight ahead
  187. float turn = 1.0f + DotProduct( -m_forward, to );
  188. Vector perp( -m_forward.y, m_forward.x, 0.0f );
  189. float side = (DotProduct( perp, to ) > 1.0f) ? 1.0f : -1.0f;
  190. if (turn > 1.0f)
  191. {
  192. // always turn one way to avoid dithering if many fish are behind us
  193. side = (m_turnClockwise) ? 1.0f : -1.0f;
  194. }
  195. float power = 1.0f - (range / maxRange);
  196. const float flockInfluence = 0.7f; // 0.3f; // 0.3
  197. m_angleChange += amount * flockInfluence * power * side * turn;
  198. }
  199. //-------------------------------------------------------------------------------------------------------------
  200. /**
  201. * Returns a value between zero (no danger of hitting an obstacle)
  202. * and one (extreme danger of hitting an obstacle).
  203. * This is used to modulate later flocking behaviors.
  204. */
  205. float CFish::Avoid( void )
  206. {
  207. const float avoidPower = 100.0f; // 50.0f; // 25.0f;
  208. //
  209. // Stay within pool bounds.
  210. // This may cause problems with pools with oddly concave portions
  211. // right at the max range.
  212. //
  213. Vector toCenter = m_pool->GetAbsOrigin() - GetAbsOrigin();
  214. const float avoidZone = 20.0f;
  215. if (toCenter.IsLengthGreaterThan( m_pool->GetMaxRange() - avoidZone ))
  216. {
  217. // turn away from edge
  218. if (DotProduct( toCenter, m_forward ) < 0.0f)
  219. {
  220. m_angleChange += (m_turnClockwise) ? -avoidPower : avoidPower;
  221. }
  222. // take total precedence over flocking
  223. return 1.0f;
  224. }
  225. trace_t result;
  226. const float sideOffset = 0.2f;
  227. float rightDanger = 0.0f;
  228. float leftDanger = 0.0f;
  229. // slightly right of forward
  230. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + m_avoidRange * (m_forward + sideOffset * m_perp), MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  231. if (result.fraction < 1.0f)
  232. {
  233. rightDanger = 1.0f - result.fraction;
  234. }
  235. // slightly left of forward
  236. UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + m_avoidRange * (m_forward - sideOffset * m_perp), MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
  237. if (result.fraction < 1.0f)
  238. {
  239. // steer away
  240. leftDanger = 1.0f - result.fraction;
  241. }
  242. // steer away - prefer one side to avoid cul-de-sacs
  243. if (m_turnClockwise)
  244. {
  245. if (rightDanger > 0.0f)
  246. {
  247. m_angleChange -= avoidPower * rightDanger;
  248. }
  249. else
  250. {
  251. m_angleChange += avoidPower * leftDanger;
  252. }
  253. }
  254. else
  255. {
  256. if (leftDanger > 0.0f)
  257. {
  258. m_angleChange += avoidPower * leftDanger;
  259. }
  260. else
  261. {
  262. m_angleChange -= avoidPower * rightDanger;
  263. }
  264. }
  265. return (leftDanger > rightDanger) ? leftDanger : rightDanger;
  266. }
  267. //-------------------------------------------------------------------------------------------------------------
  268. void CFish::Panic( void )
  269. {
  270. // start to panic
  271. m_panicTimer.Start( RandomFloat( 5.0f, 15.0f ) );
  272. m_moveTimer.Start( RandomFloat( 10.0f, 20.0f ) );
  273. m_desiredSpeed = m_panicSpeed;
  274. }
  275. //-------------------------------------------------------------------------------------------------------------
  276. /**
  277. * Invoked each server tick
  278. */
  279. void CFish::Update( float deltaT )
  280. {
  281. Vector deltaPos = GetAbsOrigin() - m_poolOrigin;
  282. const float safetyMargin = 5.0f;
  283. // pass relative position to the client
  284. // clamp them here to cover the rare cases where a fish's high velocity skirts the range limit
  285. m_x = clamp( deltaPos.x, -255.0f, 255.0f );
  286. m_y = clamp( deltaPos.y, -255.0f, 255.0f );
  287. m_z = m_poolOrigin->z;
  288. //
  289. // Dead fish just coast to a stop. All floating to the
  290. // surface and bobbing motion is handled client-side.
  291. //
  292. if (m_lifeState == LIFE_DEAD)
  293. {
  294. // don't allow fish to leave maximum range of pool
  295. if (deltaPos.IsLengthGreaterThan( m_pool->GetMaxRange() - safetyMargin ))
  296. {
  297. SetAbsVelocity( Vector( 0, 0, 0 ) );
  298. }
  299. else
  300. {
  301. // decay movement speed to zero
  302. Vector vel = GetAbsVelocity();
  303. const float drag = 1.0f;
  304. vel -= drag * vel * deltaT;
  305. SetAbsVelocity( vel );
  306. }
  307. return;
  308. }
  309. //
  310. // Living fish behavior
  311. //
  312. // periodically change our turning preference
  313. if (m_turnTimer.IsElapsed())
  314. {
  315. m_turnTimer.Start( RandomFloat( 10.0f, 30.0f ) );
  316. m_turnClockwise = !m_turnClockwise;
  317. }
  318. if (m_panicTimer.GetRemainingTime() > 0.0f)
  319. {
  320. // panicking
  321. m_desiredSpeed = m_panicSpeed;
  322. }
  323. else if (m_moveTimer.GetRemainingTime() > 0.0f)
  324. {
  325. // normal movement
  326. m_desiredSpeed = m_calmSpeed;
  327. }
  328. else if (m_goTimer.IsElapsed())
  329. {
  330. // move every so often
  331. m_goTimer.Start( RandomFloat( 10.0f, 60.0f ) );
  332. m_moveTimer.Start( RandomFloat( 2.0f, 10.0 ) );
  333. m_desiredSpeed = m_calmSpeed;
  334. }
  335. // avoid obstacles
  336. float danger = Avoid();
  337. // flock towards visible fish
  338. for( int i=0; i<m_visible.Count(); ++i )
  339. {
  340. FlockTo( m_visible[i], (1.0f - danger) );
  341. }
  342. // flock towards center of pool
  343. FlockTo( NULL, (1.0f - danger) );
  344. //
  345. // Update orientation
  346. //
  347. // limit rate of angular change - proportional to movement rate
  348. const float maxAngleChange = (25.0f + 175.0f * (m_speed/m_panicSpeed)) * deltaT;
  349. if (m_angleChange > maxAngleChange)
  350. {
  351. m_angleChange = maxAngleChange;
  352. }
  353. else if (m_angleChange < -maxAngleChange)
  354. {
  355. m_angleChange = -maxAngleChange;
  356. }
  357. m_angle += m_angleChange;
  358. m_angleChange = 0.0f;
  359. m_forward.x = cos( m_angle * M_PI/180.0f );
  360. m_forward.y = sin( m_angle * M_PI/180.0f );
  361. m_forward.z = 0.0f;
  362. m_perp.x = -m_forward.y;
  363. m_perp.y = m_forward.x;
  364. m_perp.z = 0.0f;
  365. //
  366. // Update speed
  367. //
  368. const float rate = 2.0f;
  369. m_speed += rate * (m_desiredSpeed - m_speed) * deltaT;
  370. // decay desired speed if done moving
  371. if (m_moveTimer.IsElapsed())
  372. {
  373. const float decayRate = 1.0f;
  374. m_desiredSpeed -= decayRate * deltaT;
  375. if (m_desiredSpeed < 0.0f)
  376. {
  377. m_desiredSpeed = 0.0f;
  378. }
  379. }
  380. Vector vel = m_speed * m_forward;
  381. // don't allow fish to leave maximum range of pool
  382. if (deltaPos.IsLengthGreaterThan( m_pool->GetMaxRange() - safetyMargin ))
  383. {
  384. Vector toCenter = -deltaPos;
  385. float radial = DotProduct( toCenter, vel );
  386. if (radial < 0.0f)
  387. {
  388. // heading out of range, zero the radial velocity component
  389. toCenter.NormalizeInPlace();
  390. Vector perp( -toCenter.y, toCenter.x, 0.0f );
  391. float side = DotProduct( perp, vel );
  392. vel = side * perp;
  393. }
  394. }
  395. SetAbsVelocity( vel );
  396. m_flSpeed = m_speed;
  397. }
  398. //-------------------------------------------------------------------------------------------------------------
  399. /**
  400. * Zero the visible vector
  401. */
  402. void CFish::ResetVisible( void )
  403. {
  404. m_visible.RemoveAll();
  405. }
  406. //-------------------------------------------------------------------------------------------------------------
  407. /**
  408. * Add this fish to our visible vector
  409. */
  410. void CFish::AddVisible( CFish *fish )
  411. {
  412. m_visible.AddToTail( fish );
  413. }
  414. //-------------------------------------------------------------------------------------------------------------
  415. //-------------------------------------------------------------------------------------------------------------
  416. /**
  417. * A CFishPool manages a collection of CFish, and defines where the "pool" is in the world.
  418. */
  419. LINK_ENTITY_TO_CLASS( func_fish_pool, CFishPool );
  420. BEGIN_DATADESC( CFishPool )
  421. DEFINE_FIELD( m_fishCount, FIELD_INTEGER ),
  422. DEFINE_FIELD( m_maxRange, FIELD_FLOAT ),
  423. DEFINE_FIELD( m_swimDepth, FIELD_FLOAT ),
  424. DEFINE_FIELD( m_waterLevel, FIELD_FLOAT ),
  425. DEFINE_FIELD( m_isDormant, FIELD_BOOLEAN ),
  426. DEFINE_UTLVECTOR( m_fishes, FIELD_EHANDLE ),
  427. DEFINE_THINKFUNC( Update ),
  428. END_DATADESC()
  429. //-------------------------------------------------------------------------------------------------------------
  430. CFishPool::CFishPool( void )
  431. {
  432. m_fishCount = 0;
  433. m_maxRange = 255.0f;
  434. m_swimDepth = 0.0f;
  435. m_isDormant = false;
  436. m_visTimer.Start( 0.5f );
  437. ListenForGameEvent( "player_shoot" );
  438. ListenForGameEvent( "player_footstep" );
  439. ListenForGameEvent( "weapon_fire" );
  440. ListenForGameEvent( "hegrenade_detonate" );
  441. ListenForGameEvent( "flashbang_detonate" );
  442. ListenForGameEvent( "smokegrenade_detonate" );
  443. ListenForGameEvent( "bomb_exploded" );
  444. }
  445. //-------------------------------------------------------------------------------------------------------------
  446. /**
  447. * Initialize the fish pool
  448. */
  449. void CFishPool::Spawn()
  450. {
  451. SetThink( &CFishPool::Update );
  452. SetNextThink( gpGlobals->curtime );
  453. m_waterLevel = UTIL_WaterLevel( GetAbsOrigin(), GetAbsOrigin().z, GetAbsOrigin().z + 1000.0f );
  454. trace_t result;
  455. for( int i=0; i<m_fishCount; ++i )
  456. {
  457. QAngle heading( 0.0f, RandomFloat( 0, 360.0f ), 0.0f );
  458. CFish *fish = (CFish *)Create( "fish", GetAbsOrigin(), heading, this );
  459. fish->Initialize( this, i );
  460. if (fish)
  461. {
  462. CHandle<CFish> hFish;
  463. hFish.Set( fish );
  464. m_fishes.AddToTail( hFish );
  465. }
  466. }
  467. }
  468. //-------------------------------------------------------------------------------------------------------------
  469. /**
  470. * Parse KeyValue pairs
  471. */
  472. bool CFishPool::KeyValue( const char *szKeyName, const char *szValue )
  473. {
  474. if (FStrEq( szKeyName, "fish_count" ))
  475. {
  476. m_fishCount = atoi(szValue);
  477. return true;
  478. }
  479. else if (FStrEq( szKeyName, "max_range" ))
  480. {
  481. m_maxRange = atof(szValue);
  482. if (m_maxRange <= 1.0f)
  483. {
  484. m_maxRange = 1.0f;
  485. }
  486. else if (m_maxRange > 255.0f)
  487. {
  488. // stay within 8 bits range
  489. m_maxRange = 255.0f;
  490. }
  491. return true;
  492. }
  493. else if (FStrEq( szKeyName, "model" ))
  494. {
  495. PrecacheModel( szValue );
  496. SetModelName( AllocPooledString( szValue ) );
  497. }
  498. return BaseClass::KeyValue( szKeyName, szValue );
  499. }
  500. //-------------------------------------------------------------------------------------------------------------
  501. /**
  502. * Game event processing
  503. */
  504. void CFishPool::FireGameEvent( IGameEvent *event )
  505. {
  506. CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
  507. // the fish panic
  508. const float loudRange = 500.0f;
  509. const float quietRange = 75.0f;
  510. float range = (Q_strcmp( "player_footstep", event->GetName() )) ? loudRange : quietRange;
  511. for( int i=0; i<m_fishes.Count(); ++i )
  512. {
  513. // if player is NULL, assume a game-wide event
  514. if (player && (player->GetAbsOrigin() - m_fishes[i]->GetAbsOrigin()).IsLengthGreaterThan( range ))
  515. {
  516. // event too far away to care
  517. continue;
  518. }
  519. m_fishes[i]->Panic();
  520. }
  521. }
  522. //-------------------------------------------------------------------------------------------------------------
  523. /**
  524. * Invoked each server tick
  525. */
  526. void CFishPool::Update( void )
  527. {
  528. float deltaT = 0.1f;
  529. SetNextThink( gpGlobals->curtime + deltaT );
  530. /// @todo Go dormant when no players are around to see us
  531. if (fish_dormant.GetBool())
  532. {
  533. if (!m_isDormant)
  534. {
  535. // stop all the fish
  536. for( int i=0; i<m_fishes.Count(); ++i )
  537. {
  538. m_fishes[i]->SetAbsVelocity( Vector( 0, 0, 0 ) );
  539. }
  540. m_isDormant = true;
  541. }
  542. return;
  543. }
  544. else
  545. {
  546. m_isDormant = false;
  547. }
  548. // update fish to fish visibility
  549. if (m_visTimer.IsElapsed())
  550. {
  551. m_visTimer.Reset();
  552. int i, j;
  553. trace_t result;
  554. // reset each fishes vis list
  555. for( i=0; i<m_fishes.Count(); ++i )
  556. {
  557. m_fishes[i]->ResetVisible();
  558. }
  559. // build new vis lists - line of sight is symmetric
  560. for( i=0; i<m_fishes.Count(); ++i )
  561. {
  562. if (!m_fishes[i]->IsAlive())
  563. continue;
  564. for( j=i+1; j<m_fishes.Count(); ++j )
  565. {
  566. if (!m_fishes[j]->IsAlive())
  567. continue;
  568. UTIL_TraceLine( m_fishes[i]->GetAbsOrigin(), m_fishes[j]->GetAbsOrigin(), MASK_PLAYERSOLID, m_fishes[i], COLLISION_GROUP_NONE, &result );
  569. if (result.fraction >= 1.0f)
  570. {
  571. // the fish can see each other
  572. m_fishes[i]->AddVisible( m_fishes[j] );
  573. m_fishes[j]->AddVisible( m_fishes[i] );
  574. }
  575. }
  576. }
  577. }
  578. // simulate the fishes behavior
  579. for( int i=0; i<m_fishes.Count(); ++i )
  580. {
  581. m_fishes[i]->Update( deltaT );
  582. }
  583. }