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
733 lines
18 KiB
//========= 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 ); |
|
} |
|
} |
|
|
|
|