From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/fish.cpp | 1466 +++++++++++++++++++++---------------------- 1 file changed, 733 insertions(+), 733 deletions(-) (limited to 'mp/src/game/server/fish.cpp') diff --git a/mp/src/game/server/fish.cpp b/mp/src/game/server/fish.cpp index 0a623199..e6620068 100644 --- a/mp/src/game/server/fish.cpp +++ b/mp/src/game/server/fish.cpp @@ -1,733 +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 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; iInitialize( this, i ); - - if (fish) - { - CHandle 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; iGetAbsOrigin() - 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; iSetAbsVelocity( 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; iResetVisible(); - } - - // build new vis lists - line of sight is symmetric - for( i=0; iIsAlive()) - continue; - - for( j=i+1; jIsAlive()) - 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; iUpdate( deltaT ); - } -} - +//========= 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 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; iInitialize( this, i ); + + if (fish) + { + CHandle 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; iGetAbsOrigin() - 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; iSetAbsVelocity( 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; iResetVisible(); + } + + // build new vis lists - line of sight is symmetric + for( i=0; iIsAlive()) + continue; + + for( j=i+1; jIsAlive()) + 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; iUpdate( deltaT ); + } +} + -- cgit v1.2.3