diff options
Diffstat (limited to 'mp/src/game/server/fish.cpp')
| -rw-r--r-- | mp/src/game/server/fish.cpp | 733 |
1 files changed, 733 insertions, 0 deletions
diff --git a/mp/src/game/server/fish.cpp b/mp/src/game/server/fish.cpp new file mode 100644 index 00000000..0a623199 --- /dev/null +++ b/mp/src/game/server/fish.cpp @@ -0,0 +1,733 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+// fish.cpp
+// Simple fish behavior
+// Author: Michael S. Booth, April 2005
+
+#include "cbase.h"
+#include "fish.h"
+#include "saverestore_utlvector.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+ConVar fish_dormant( "fish_dormant", "0", FCVAR_REPLICATED | FCVAR_CHEAT, "Turns off interactive fish behavior. Fish become immobile and unresponsive." );
+
+
+//-----------------------------------------------------------------------------------------------------
+LINK_ENTITY_TO_CLASS( fish, CFish );
+
+
+//-----------------------------------------------------------------------------------------------------
+BEGIN_DATADESC( CFish )
+ DEFINE_FIELD( m_pool, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_id, FIELD_INTEGER ),
+ DEFINE_FIELD( m_angle, FIELD_FLOAT ),
+ DEFINE_FIELD( m_angleChange, FIELD_FLOAT ),
+ DEFINE_FIELD( m_forward, FIELD_VECTOR ),
+ DEFINE_FIELD( m_perp, FIELD_VECTOR ),
+ DEFINE_FIELD( m_poolOrigin, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_waterLevel, FIELD_FLOAT ),
+ DEFINE_FIELD( m_speed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_desiredSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_calmSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_panicSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_avoidRange, FIELD_FLOAT ),
+ DEFINE_FIELD( m_turnClockwise, FIELD_BOOLEAN ),
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------------------------------
+/**
+ * Send fish position relative to pool origin
+ */
+void SendProxy_FishOriginX( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CFish *fish = (CFish *)pStruct;
+ Assert( fish );
+
+ const Vector &v = fish->GetAbsOrigin();
+ Vector origin = fish->m_poolOrigin;
+
+ pOut->m_Float = v.x - origin.x;
+}
+
+void SendProxy_FishOriginY( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ CFish *fish = (CFish *)pStruct;
+ Assert( fish );
+
+ const Vector &v = fish->GetAbsOrigin();
+ Vector origin = fish->m_poolOrigin;
+
+ pOut->m_Float = v.y - origin.y;
+}
+
+// keep angle in normalized range when sending it
+void SendProxy_FishAngle( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
+{
+ float value = *((float *)pData);
+
+ while( value > 360.0f )
+ value -= 360.0f;
+
+ while (value < 0.0f)
+ value += 360.0f;
+
+ pOut->m_Float = value;
+}
+
+
+/**
+ * NOTE: Do NOT use SPROP_CHANGES_OFTEN, as it will reorder this list.
+ * The pool origin must arrive befoore m_x and m_y or the fish will
+ * respawn at the origin and zip back to their proper places.
+ */
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CFish, DT_CFish )
+
+ SendPropVector( SENDINFO(m_poolOrigin), -1, SPROP_COORD, 0.0f, HIGH_DEFAULT ), // only sent once
+
+ SendPropFloat( SENDINFO(m_angle), 7, 0 /*SPROP_CHANGES_OFTEN*/, 0.0f, 360.0f, SendProxy_FishAngle ),
+
+ SendPropFloat( SENDINFO(m_x), 7, 0 /*SPROP_CHANGES_OFTEN*/, -255.0f, 255.0f ),
+ SendPropFloat( SENDINFO(m_y), 7, 0 /*SPROP_CHANGES_OFTEN*/, -255.0f, 255.0f ),
+ SendPropFloat( SENDINFO(m_z), -1, SPROP_COORD ), // only sent once
+
+ SendPropModelIndex( SENDINFO(m_nModelIndex) ),
+ SendPropInt( SENDINFO(m_lifeState) ),
+
+ SendPropFloat( SENDINFO(m_waterLevel) ), // only sent once
+
+END_SEND_TABLE()
+
+
+
+//-------------------------------------------------------------------------------------------------------------
+CFish::CFish( void )
+{
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+CFish::~CFish()
+{
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+void CFish::Initialize( CFishPool *pool, unsigned int id )
+{
+ m_pool = pool;
+ m_id = id;
+
+ m_poolOrigin = pool->GetAbsOrigin();
+ m_waterLevel = pool->GetWaterLevel();
+
+ // pass relative position to the client
+ Vector deltaPos = GetAbsOrigin() - m_poolOrigin;
+ m_x = deltaPos.x;
+ m_y = deltaPos.y;
+ m_z = m_poolOrigin->z;
+
+ SetModel( pool->GetModelName().ToCStr() );
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+void CFish::Spawn( void )
+{
+ Precache();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_NOT_SOLID | FSOLID_TRIGGER );
+ SetMoveType( MOVETYPE_FLY );
+
+ m_angle = RandomFloat( 0.0f, 360.0f );
+ m_angleChange = 0.0f;
+
+ m_forward = Vector( 1.0f, 0.0, 0.0f );
+ m_perp.x = -m_forward.y;
+ m_perp.y = m_forward.x;
+ m_perp.z = 0.0f;
+
+ m_speed = 0.0f;
+ m_calmSpeed = RandomFloat( 10.0f, 20.0f );
+ m_panicSpeed = m_calmSpeed * RandomFloat( 4.0f, 5.0f );
+ m_desiredSpeed = m_calmSpeed;
+
+ m_turnClockwise = (RandomInt( 0, 100 ) < 50);
+
+ m_avoidRange = RandomFloat( 40.0f, 75.0f );
+
+ m_iHealth = 1;
+ m_iMaxHealth = 1;
+ m_takedamage = DAMAGE_YES;
+
+ // spread out a bit
+ m_disperseTimer.Start( RandomFloat( 0.0f, 10.0f ) );
+ m_goTimer.Start( RandomFloat( 10.0f, 60.0f ) );
+ m_moveTimer.Start( RandomFloat( 2.0f, 10.0 ) );
+ m_desiredSpeed = m_calmSpeed;
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+void CFish::Event_Killed( const CTakeDamageInfo &info )
+{
+ m_takedamage = DAMAGE_NO;
+ m_lifeState = LIFE_DEAD;
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * In contact with "other"
+ */
+void CFish::Touch( CBaseEntity *other )
+{
+ if (other && other->IsPlayer())
+ {
+ // touched a Player - panic!
+ Panic();
+ }
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Influence my motion to flock with other nearby fish
+ * 'amount' ranges from zero to one, representing the amount of flocking influence allowed
+ * If 'other' is NULL, flock to the center of the pool.
+ */
+void CFish::FlockTo( CFish *other, float amount )
+{
+ // allow fish to disperse a bit at round start
+ if (!m_disperseTimer.IsElapsed())
+ return;
+
+ const float maxRange = (other) ? 100.0f : 300.0f;
+
+ Vector to = (other) ? (other->GetAbsOrigin() - GetAbsOrigin()) : (m_pool->GetAbsOrigin() - GetAbsOrigin());
+ float range = to.NormalizeInPlace();
+
+ if (range > maxRange)
+ return;
+
+ // if they are close and we are moving together, avoid them
+ const float avoidRange = 25.0f;
+ if (other && range < avoidRange)
+ {
+ // compute their relative velocity to us
+ Vector relVel = other->GetAbsVelocity() - GetAbsVelocity();
+
+ if (DotProduct( to, relVel ) < 0.0f)
+ {
+ const float avoidPower = 5.0f;
+
+ // their comin' right at us! - avoid
+ if (DotProduct( m_perp, to ) > 0.0f)
+ {
+ m_angleChange -= avoidPower * (1.0f - range/avoidRange);
+ }
+ else
+ {
+ m_angleChange += avoidPower * (1.0f - range/avoidRange);
+ }
+ return;
+ }
+ }
+
+ // turn is 2 if 'other' is behind us, 1 perpendicular, and 0 straight ahead
+ float turn = 1.0f + DotProduct( -m_forward, to );
+
+ Vector perp( -m_forward.y, m_forward.x, 0.0f );
+ float side = (DotProduct( perp, to ) > 1.0f) ? 1.0f : -1.0f;
+
+ if (turn > 1.0f)
+ {
+ // always turn one way to avoid dithering if many fish are behind us
+ side = (m_turnClockwise) ? 1.0f : -1.0f;
+ }
+
+ float power = 1.0f - (range / maxRange);
+
+ const float flockInfluence = 0.7f; // 0.3f; // 0.3
+ m_angleChange += amount * flockInfluence * power * side * turn;
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Returns a value between zero (no danger of hitting an obstacle)
+ * and one (extreme danger of hitting an obstacle).
+ * This is used to modulate later flocking behaviors.
+ */
+float CFish::Avoid( void )
+{
+ const float avoidPower = 100.0f; // 50.0f; // 25.0f;
+
+ //
+ // Stay within pool bounds.
+ // This may cause problems with pools with oddly concave portions
+ // right at the max range.
+ //
+ Vector toCenter = m_pool->GetAbsOrigin() - GetAbsOrigin();
+ const float avoidZone = 20.0f;
+ if (toCenter.IsLengthGreaterThan( m_pool->GetMaxRange() - avoidZone ))
+ {
+ // turn away from edge
+ if (DotProduct( toCenter, m_forward ) < 0.0f)
+ {
+ m_angleChange += (m_turnClockwise) ? -avoidPower : avoidPower;
+ }
+
+ // take total precedence over flocking
+ return 1.0f;
+ }
+
+ trace_t result;
+ const float sideOffset = 0.2f;
+
+ float rightDanger = 0.0f;
+ float leftDanger = 0.0f;
+
+ // slightly right of forward
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + m_avoidRange * (m_forward + sideOffset * m_perp), MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+ if (result.fraction < 1.0f)
+ {
+ rightDanger = 1.0f - result.fraction;
+ }
+
+ // slightly left of forward
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + m_avoidRange * (m_forward - sideOffset * m_perp), MASK_PLAYERSOLID, this, COLLISION_GROUP_NONE, &result );
+ if (result.fraction < 1.0f)
+ {
+ // steer away
+ leftDanger = 1.0f - result.fraction;
+ }
+
+ // steer away - prefer one side to avoid cul-de-sacs
+ if (m_turnClockwise)
+ {
+ if (rightDanger > 0.0f)
+ {
+ m_angleChange -= avoidPower * rightDanger;
+ }
+ else
+ {
+ m_angleChange += avoidPower * leftDanger;
+ }
+ }
+ else
+ {
+ if (leftDanger > 0.0f)
+ {
+ m_angleChange += avoidPower * leftDanger;
+ }
+ else
+ {
+ m_angleChange -= avoidPower * rightDanger;
+ }
+ }
+
+
+ return (leftDanger > rightDanger) ? leftDanger : rightDanger;
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+void CFish::Panic( void )
+{
+ // start to panic
+ m_panicTimer.Start( RandomFloat( 5.0f, 15.0f ) );
+ m_moveTimer.Start( RandomFloat( 10.0f, 20.0f ) );
+ m_desiredSpeed = m_panicSpeed;
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked each server tick
+ */
+void CFish::Update( float deltaT )
+{
+ Vector deltaPos = GetAbsOrigin() - m_poolOrigin;
+ const float safetyMargin = 5.0f;
+
+ // pass relative position to the client
+ // clamp them here to cover the rare cases where a fish's high velocity skirts the range limit
+ m_x = clamp( deltaPos.x, -255.0f, 255.0f );
+ m_y = clamp( deltaPos.y, -255.0f, 255.0f );
+ m_z = m_poolOrigin->z;
+
+
+ //
+ // Dead fish just coast to a stop. All floating to the
+ // surface and bobbing motion is handled client-side.
+ //
+ if (m_lifeState == LIFE_DEAD)
+ {
+ // don't allow fish to leave maximum range of pool
+ if (deltaPos.IsLengthGreaterThan( m_pool->GetMaxRange() - safetyMargin ))
+ {
+ SetAbsVelocity( Vector( 0, 0, 0 ) );
+ }
+ else
+ {
+ // decay movement speed to zero
+ Vector vel = GetAbsVelocity();
+
+ const float drag = 1.0f;
+ vel -= drag * vel * deltaT;
+
+ SetAbsVelocity( vel );
+ }
+
+ return;
+ }
+
+
+ //
+ // Living fish behavior
+ //
+
+ // periodically change our turning preference
+ if (m_turnTimer.IsElapsed())
+ {
+ m_turnTimer.Start( RandomFloat( 10.0f, 30.0f ) );
+ m_turnClockwise = !m_turnClockwise;
+ }
+
+ if (m_panicTimer.GetRemainingTime() > 0.0f)
+ {
+ // panicking
+ m_desiredSpeed = m_panicSpeed;
+ }
+ else if (m_moveTimer.GetRemainingTime() > 0.0f)
+ {
+ // normal movement
+ m_desiredSpeed = m_calmSpeed;
+ }
+ else if (m_goTimer.IsElapsed())
+ {
+ // move every so often
+ m_goTimer.Start( RandomFloat( 10.0f, 60.0f ) );
+ m_moveTimer.Start( RandomFloat( 2.0f, 10.0 ) );
+ m_desiredSpeed = m_calmSpeed;
+ }
+
+ // avoid obstacles
+ float danger = Avoid();
+
+ // flock towards visible fish
+ for( int i=0; i<m_visible.Count(); ++i )
+ {
+ FlockTo( m_visible[i], (1.0f - danger) );
+ }
+
+ // flock towards center of pool
+ FlockTo( NULL, (1.0f - danger) );
+
+
+ //
+ // Update orientation
+ //
+
+ // limit rate of angular change - proportional to movement rate
+ const float maxAngleChange = (25.0f + 175.0f * (m_speed/m_panicSpeed)) * deltaT;
+ if (m_angleChange > maxAngleChange)
+ {
+ m_angleChange = maxAngleChange;
+ }
+ else if (m_angleChange < -maxAngleChange)
+ {
+ m_angleChange = -maxAngleChange;
+ }
+
+ m_angle += m_angleChange;
+ m_angleChange = 0.0f;
+
+ m_forward.x = cos( m_angle * M_PI/180.0f );
+ m_forward.y = sin( m_angle * M_PI/180.0f );
+ m_forward.z = 0.0f;
+
+ m_perp.x = -m_forward.y;
+ m_perp.y = m_forward.x;
+ m_perp.z = 0.0f;
+
+ //
+ // Update speed
+ //
+ const float rate = 2.0f;
+ m_speed += rate * (m_desiredSpeed - m_speed) * deltaT;
+
+ // decay desired speed if done moving
+ if (m_moveTimer.IsElapsed())
+ {
+ const float decayRate = 1.0f;
+ m_desiredSpeed -= decayRate * deltaT;
+ if (m_desiredSpeed < 0.0f)
+ {
+ m_desiredSpeed = 0.0f;
+ }
+ }
+
+ Vector vel = m_speed * m_forward;
+
+ // don't allow fish to leave maximum range of pool
+ if (deltaPos.IsLengthGreaterThan( m_pool->GetMaxRange() - safetyMargin ))
+ {
+ Vector toCenter = -deltaPos;
+
+ float radial = DotProduct( toCenter, vel );
+ if (radial < 0.0f)
+ {
+ // heading out of range, zero the radial velocity component
+ toCenter.NormalizeInPlace();
+ Vector perp( -toCenter.y, toCenter.x, 0.0f );
+
+ float side = DotProduct( perp, vel );
+
+ vel = side * perp;
+ }
+ }
+
+ SetAbsVelocity( vel );
+
+ m_flSpeed = m_speed;
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Zero the visible vector
+ */
+void CFish::ResetVisible( void )
+{
+ m_visible.RemoveAll();
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Add this fish to our visible vector
+ */
+void CFish::AddVisible( CFish *fish )
+{
+ m_visible.AddToTail( fish );
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * A CFishPool manages a collection of CFish, and defines where the "pool" is in the world.
+ */
+
+LINK_ENTITY_TO_CLASS( func_fish_pool, CFishPool );
+
+BEGIN_DATADESC( CFishPool )
+
+ DEFINE_FIELD( m_fishCount, FIELD_INTEGER ),
+ DEFINE_FIELD( m_maxRange, FIELD_FLOAT ),
+ DEFINE_FIELD( m_swimDepth, FIELD_FLOAT ),
+ DEFINE_FIELD( m_waterLevel, FIELD_FLOAT ),
+ DEFINE_FIELD( m_isDormant, FIELD_BOOLEAN ),
+ DEFINE_UTLVECTOR( m_fishes, FIELD_EHANDLE ),
+
+ DEFINE_THINKFUNC( Update ),
+
+END_DATADESC()
+
+
+//-------------------------------------------------------------------------------------------------------------
+CFishPool::CFishPool( void )
+{
+ m_fishCount = 0;
+ m_maxRange = 255.0f;
+ m_swimDepth = 0.0f;
+ m_isDormant = false;
+
+ m_visTimer.Start( 0.5f );
+
+ ListenForGameEvent( "player_shoot" );
+ ListenForGameEvent( "player_footstep" );
+ ListenForGameEvent( "weapon_fire" );
+ ListenForGameEvent( "hegrenade_detonate" );
+ ListenForGameEvent( "flashbang_detonate" );
+ ListenForGameEvent( "smokegrenade_detonate" );
+ ListenForGameEvent( "bomb_exploded" );
+}
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Initialize the fish pool
+ */
+void CFishPool::Spawn()
+{
+ SetThink( &CFishPool::Update );
+ SetNextThink( gpGlobals->curtime );
+
+ m_waterLevel = UTIL_WaterLevel( GetAbsOrigin(), GetAbsOrigin().z, GetAbsOrigin().z + 1000.0f );
+
+ trace_t result;
+ for( int i=0; i<m_fishCount; ++i )
+ {
+ QAngle heading( 0.0f, RandomFloat( 0, 360.0f ), 0.0f );
+
+ CFish *fish = (CFish *)Create( "fish", GetAbsOrigin(), heading, this );
+ fish->Initialize( this, i );
+
+ if (fish)
+ {
+ CHandle<CFish> hFish;
+ hFish.Set( fish );
+ m_fishes.AddToTail( hFish );
+ }
+ }
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Parse KeyValue pairs
+ */
+bool CFishPool::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if (FStrEq( szKeyName, "fish_count" ))
+ {
+ m_fishCount = atoi(szValue);
+ return true;
+ }
+ else if (FStrEq( szKeyName, "max_range" ))
+ {
+ m_maxRange = atof(szValue);
+ if (m_maxRange <= 1.0f)
+ {
+ m_maxRange = 1.0f;
+ }
+ else if (m_maxRange > 255.0f)
+ {
+ // stay within 8 bits range
+ m_maxRange = 255.0f;
+ }
+
+ return true;
+ }
+ else if (FStrEq( szKeyName, "model" ))
+ {
+ PrecacheModel( szValue );
+ SetModelName( AllocPooledString( szValue ) );
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Game event processing
+ */
+void CFishPool::FireGameEvent( IGameEvent *event )
+{
+ CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
+
+ // the fish panic
+ const float loudRange = 500.0f;
+ const float quietRange = 75.0f;
+
+ float range = (Q_strcmp( "player_footstep", event->GetName() )) ? loudRange : quietRange;
+
+ for( int i=0; i<m_fishes.Count(); ++i )
+ {
+ // if player is NULL, assume a game-wide event
+ if (player && (player->GetAbsOrigin() - m_fishes[i]->GetAbsOrigin()).IsLengthGreaterThan( range ))
+ {
+ // event too far away to care
+ continue;
+ }
+
+ m_fishes[i]->Panic();
+ }
+}
+
+
+//-------------------------------------------------------------------------------------------------------------
+/**
+ * Invoked each server tick
+ */
+void CFishPool::Update( void )
+{
+ float deltaT = 0.1f;
+ SetNextThink( gpGlobals->curtime + deltaT );
+
+ /// @todo Go dormant when no players are around to see us
+
+ if (fish_dormant.GetBool())
+ {
+ if (!m_isDormant)
+ {
+ // stop all the fish
+ for( int i=0; i<m_fishes.Count(); ++i )
+ {
+ m_fishes[i]->SetAbsVelocity( Vector( 0, 0, 0 ) );
+ }
+
+ m_isDormant = true;
+ }
+
+ return;
+ }
+ else
+ {
+ m_isDormant = false;
+ }
+
+ // update fish to fish visibility
+ if (m_visTimer.IsElapsed())
+ {
+ m_visTimer.Reset();
+
+ int i, j;
+ trace_t result;
+
+ // reset each fishes vis list
+ for( i=0; i<m_fishes.Count(); ++i )
+ {
+ m_fishes[i]->ResetVisible();
+ }
+
+ // build new vis lists - line of sight is symmetric
+ for( i=0; i<m_fishes.Count(); ++i )
+ {
+ if (!m_fishes[i]->IsAlive())
+ continue;
+
+ for( j=i+1; j<m_fishes.Count(); ++j )
+ {
+ if (!m_fishes[j]->IsAlive())
+ continue;
+
+ UTIL_TraceLine( m_fishes[i]->GetAbsOrigin(), m_fishes[j]->GetAbsOrigin(), MASK_PLAYERSOLID, m_fishes[i], COLLISION_GROUP_NONE, &result );
+ if (result.fraction >= 1.0f)
+ {
+ // the fish can see each other
+ m_fishes[i]->AddVisible( m_fishes[j] );
+ m_fishes[j]->AddVisible( m_fishes[i] );
+ }
+ }
+ }
+ }
+
+ // simulate the fishes behavior
+ for( int i=0; i<m_fishes.Count(); ++i )
+ {
+ m_fishes[i]->Update( deltaT );
+ }
+}
+
|