Counter Strike : Global Offensive Source Code
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.
 
 
 
 
 
 

1283 lines
30 KiB

// chicken.cpp
// An interactive, shootable chicken
#include "cbase.h"
#include "chicken.h"
#include "cs_player.h"
#include "cs_gamerules.h"
#include "particle_parse.h"
#include "engine/IEngineSound.h"
#include "cs_simple_hostage.h"
#include "cs_player_resource.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
BEGIN_DATADESC( CChicken )
DEFINE_ENTITYFUNC( ChickenTouch ),
DEFINE_THINKFUNC( ChickenThink ),
DEFINE_USEFUNC( ChickenUse ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CChicken, DT_CChicken )
SendPropBool( SENDINFO( m_jumpedThisFrame ) ),
SendPropEHandle( SENDINFO( m_leader ) ),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( chicken, CChicken );
PRECACHE_REGISTER( chicken );
class HostagePathCost;
extern ConVar hostage_debug;
#define CHICKEN_ZOMBIE_SPAWN_DURATION 3.5f
#define CHICKEN_DISTANCE_RUN 200.0f
#define CHICKEN_DISTANCE_WALK 100.0f
//-----------------------------------------------------------------------
CChicken::CChicken()
{
}
//-----------------------------------------------------------------------
CChicken::~CChicken()
{
}
//-----------------------------------------------------------------------
void CChicken::Precache( void )
{
SetModelName( MAKE_STRING( "models/chicken/chicken.mdl" ) ); // prop precache wants this
BaseClass::Precache();
PrecacheModel( "models/chicken/chicken.mdl" );
PrecacheModel( "models/chicken/chicken_zombie.mdl" );
PrecacheModel( "models/chicken/chicken_gone.mdl" );
PrecacheModel( "models/antlers/antlers.mdl" );
PrecacheScriptSound( "Chicken.Idle" );
PrecacheScriptSound( "Chicken.Panic" );
PrecacheScriptSound( "Chicken.Fly" );
PrecacheScriptSound( "Chicken.FlapWings" );
PrecacheScriptSound( "Chicken.Death" );
PrecacheScriptSound( "Chicken.ZombieRez" );
PrecacheParticleSystem( "weapon_confetti_omni" );
PrecacheParticleSystem( "chicken_rez" );
PrecacheParticleSystem( "chicken_gone_crumble_halloween" );
PrecacheParticleSystem( "chicken_gone_zombie" );
PrecacheParticleSystem( "impact_helmet_headshot" );
}
//-----------------------------------------------------------------------
void CChicken::Spawn( void )
{
SetModel( "models/chicken/chicken.mdl" );
BaseClass::Spawn();
SetNextThink( gpGlobals->curtime );
SetThink( &CChicken::ChickenThink );
SetTouch( &CChicken::ChickenTouch );
SetUse( &CChicken::ChickenUse );
SetSolid( SOLID_BBOX );
SetMoveType( MOVETYPE_FLYGRAVITY );
SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
const model_t *pModel = modelinfo->GetModel( GetModelIndex() );
if ( pModel )
{
Vector mins, maxs;
modelinfo->GetModelBounds( pModel, mins, maxs );
mins.z = 0.0f;
SetCollisionBounds( mins, maxs );
}
SetGravity( 1.0 );
SetHealth( 1 );
SetMaxHealth( 1 );
m_takedamage = DAMAGE_YES;
Idle();
m_fleeFrom = NULL;
m_updateTimer.Invalidate();
m_reuseTimer.Invalidate( );
m_moveRateThrottleTimer.Invalidate( );
m_stuckAnchor = GetAbsOrigin();
m_stuckTimer.Start( 1.0f );
m_isOnGround = false;
m_startleTimer.Invalidate();
ListenForGameEvent( "weapon_fire" );
//ListenForGameEvent( "bullet_impact" );
if ( CSGameRules() && CSGameRules()->IsCSGOBirthday() )
{
SetBodygroup( 1, 1 ); // birthday hat
}
m_leader = INVALID_EHANDLE;
m_reuseTimer.Invalidate( );
m_hasBeenUsed = false;
m_jumpedThisFrame = false;
m_path.Invalidate( );
m_repathTimer.Invalidate( );
m_pathFollower.Reset( );
m_pathFollower.SetPath( &m_path );
m_pathFollower.SetImprov( this );
m_lastKnownArea = NULL;
// Need to make sure the hostages are on the ground when they spawn
// Vector GroundPos = DropToGround( this, GetAbsOrigin( ), HOSTAGE_BBOX_VEC_MIN, HOSTAGE_BBOX_VEC_MAX );
// SetAbsOrigin( GroundPos );
m_bInJump = false;
m_flLastJumpTime = 0;
m_inhibitObstacleAvoidanceTimer.Invalidate( );
m_isWaitingForLeader = false;
m_lastLeaderID = 0;
m_flActiveFollowStartTime = 0;
}
//-----------------------------------------------------------------------
void CChicken::ChickenTouch( CBaseEntity *pOther )
{
if ( !CSGameRules() || CSGameRules()->IsPlayingAnyCompetitiveStrictRuleset() )
{
Flee( pOther, RandomFloat( 2.0f, 3.0f ) );
}
}
bool CChicken::IsFollowingSomeone( void )
{
return ( m_leader.m_Value != NULL );
}
//-----------------------------------------------------------------------------------------------------
bool CChicken::IsFollowing( const CBaseEntity *entity )
{
return ( m_leader.m_Value == entity );
}
//-----------------------------------------------------------------------------------------------------
bool CChicken::IsOnGround( void ) const
{
return ( GetFlags( ) & FL_ONGROUND );
}
//-----------------------------------------------------------------------------------------------------
/**
* Begin following "leader"
*/
void CChicken::Follow( CCSPlayer *leader )
{
m_lastLeaderID = 0;
if ( leader )
{
leader->IncrementNumFollowers( );
m_lastLeaderID = leader->GetUserID( );
m_leader = leader;
}
else
{
m_leader = INVALID_EHANDLE;
}
m_leader = leader;
m_isWaitingForLeader = false;
m_flActiveFollowStartTime = gpGlobals->curtime;
m_moveRateThrottleTimer.Start( 1.0f);
}
//-----------------------------------------------------------------------------------------------------
/**
* Return our leader, or NULL
*/
CCSPlayer *CChicken::GetLeader( void ) const
{
return ToCSPlayer( m_leader.m_Value );
}
//-----------------------------------------------------------------------------------------------------
/**
* Invoked when a chicken is "used" by a player
*/
void CChicken::ChickenUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
CCSPlayer *pPlayer = ToCSPlayer( pActivator );
if ( !pPlayer )
return;
// limit use range
float useRange = 1000.0f;
Vector to = pActivator->GetAbsOrigin( ) - GetAbsOrigin( );
if ( to.IsLengthGreaterThan( useRange ) )
{
return;
}
SetChickenStartFollowingPlayer( pPlayer );
}
void CChicken::SetChickenStartFollowingPlayer( CCSPlayer *pPlayer )
{
// throttle how often leader can change
if ( !m_reuseTimer.IsElapsed( ) )
{
return;
}
// if we are already following the player who used us, stop following
if ( IsFollowing( pPlayer ) )
{
Follow( NULL );
Idle( );
EmitSound( "Chicken.Idle" );
}
else
{
// if we're already following a CT, ignore new uses
if ( IsFollowingSomeone( ) )
{
return;
}
// start following
Follow( pPlayer );
EmitSound( "Chicken.FlapWings" );
Jump( 50.0f );
}
m_reuseTimer.Start( 1.0f );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Rotate body to face towards "target"
*/
void CChicken::FaceTowards( const Vector &target, float deltaT )
{
Vector to = target - GetFeet( );
to.z = 0.0f;
QAngle desiredAngles;
VectorAngles( to, desiredAngles );
QAngle angles = GetAbsAngles( );
// The animstate system for hostages will smooth out the transition to this direction, so no need to double smooth it here.
angles.y = desiredAngles.y;
SetAbsAngles( angles );
}
int CChicken::OnTakeDamage( const CTakeDamageInfo &info )
{
/*
if ( EconHolidays_IsHolidayActive( kHoliday_Halloween ) )
{
// if we recently turned into a zombie, ignore damage for CHICKEN_ZOMBIE_SPAWN_DURATION seconds.
if ( IsZombie() )
{
if ( (gpGlobals->curtime - m_flWhenZombified) > CHICKEN_ZOMBIE_SPAWN_DURATION )
{
return BaseClass::OnTakeDamage( info );
}
else
{
return 0;
}
}
else
{
if ( m_activity != ACT_GLIDE && m_isOnGround )
{
//when there's no more room in chicken-hell... turn into a chicken zombie!
Zombify();
SetModel( "models/chicken/chicken_zombie.mdl" );
m_activity = ACT_CLIMB_UP;
ResetSequence( LookupSequence( "spawn" ) );
ResetSequenceInfo();
ForceCycle( 0 );
DispatchParticleEffect( "chicken_gone_crumble_halloween", GetAbsOrigin() + Vector( 0, 0, 8 ), QAngle( 0, 0, 0 ) );
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
trace_t tr;
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -16 ), MASK_PLAYERSOLID, &traceFilter, &tr );
Vector vecPos, vecVel, vecNormal;
QAngle angNormal;
vecNormal = tr.DidHit() ? tr.plane.normal : Vector( 0, 0, 0 );
//vecNormal.y *= -1;
//vecNormal.z *= -1;
VectorAngles( vecNormal, angNormal );
angNormal.x += 90;
DispatchParticleEffect( "chicken_rez", GetAbsOrigin(), angNormal );
CSoundParameters params;
if ( GetParametersForSound( "Chicken.ZombieRez", params, NULL ) )
{
EmitSound_t ep( params );
ep.m_pOrigin = &GetAbsOrigin();
CBroadcastRecipientFilter filter;
EmitSound( filter, SOUND_FROM_WORLD, ep );
}
SetSolid( SOLID_NONE );
return 0;
}
else
{
return BaseClass::OnTakeDamage( info );
}
}
}
else
*/
{
return BaseClass::OnTakeDamage( info );
}
}
//-----------------------------------------------------------------------
void CChicken::Event_Killed( const CTakeDamageInfo &info )
{
EmitSound( "Chicken.Death" );
if ( CSGameRules() && CSGameRules()->IsCSGOBirthday() )
DispatchParticleEffect( "weapon_confetti_omni", GetAbsOrigin(), QAngle( 0, 0, 0 ) );
if ( IsZombie() )
DispatchParticleEffect( "chicken_gone_zombie", GetAbsOrigin() + Vector( 0, 0, 8 ), QAngle( 0, 0, 0 ) );
if ( IsFollowingSomeone( ) )
{
CSingleUserRecipientFilter leaderfilter( ToCSPlayer( m_leader.m_Value ) );
leaderfilter.MakeReliable( );
float flFollowDuration = gpGlobals->curtime - m_flActiveFollowStartTime;
UTIL_ClientPrintFilter( leaderfilter, HUD_PRINTTALK, "#Pet_Killed", CFmtStr( "%.0f", flFollowDuration ).Get() );
}
BaseClass::Event_Killed( info );
}
//-----------------------------------------------------------------------
void CChicken::FireGameEvent( IGameEvent *event )
{
// all the events we care about scare us
if ( m_startleTimer.HasStarted() )
{
// we're already scared
return;
}
if ( event->GetBool( "silenced" ) )
{
// Silenced weapons don't scare chickens
return;
}
CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) );
if ( player )
{
const float fleeGunfireRange = 1000.0f;
Vector toPlayer = player->GetAbsOrigin() - GetAbsOrigin();
if ( toPlayer.IsLengthLessThan( fleeGunfireRange ) )
{
m_startleTimer.Start( RandomFloat( 0.1f, 0.5f ) );
m_fleeFrom = player;
}
}
}
//-----------------------------------------------------------------------
void CChicken::Idle()
{
// m_leader = NULL;
m_activity = ACT_IDLE;
m_activityTimer.Start( RandomFloat( 0.5, 3.0f ) );
SetSequence( SelectWeightedSequence( m_activity ) );
ResetSequenceInfo();
m_pathFollower.ResetStuck( );
}
//-----------------------------------------------------------------------
void CChicken::Walk()
{
m_activity = ACT_WALK;
m_activityTimer.Start( RandomFloat( 0.5, 3.0f ) );
m_turnRate = RandomFloat( -45.0f, 45.0f );
SetSequence( SelectWeightedSequence( m_activity ) );
ResetSequenceInfo();
}
//-----------------------------------------------------------------------
void CChicken::Flee( CBaseEntity *fleeFrom, float duration )
{
if ( m_activity != ACT_RUN || m_vocalizeTimer.IsElapsed() )
{
// throttle interval between vocalizations
m_vocalizeTimer.Start( RandomFloat( 0.5f, 1.0f ) );
EmitSound( "Chicken.Panic" );
}
m_activity = ACT_RUN;
m_activityTimer.Start( duration );
m_turnRate = 0.0f;
m_fleeFrom = fleeFrom;
SetSequence( SelectWeightedSequence( m_activity ) );
ResetSequenceInfo();
}
//-----------------------------------------------------------------------
void CChicken::Fly()
{
if ( m_activity != ACT_GLIDE || m_vocalizeTimer.IsElapsed() )
{
// throttle interval between vocalizations
m_vocalizeTimer.Start( RandomFloat( 0.5f, 1.0f ) );
EmitSound( "Chicken.Fly" );
}
m_activity = ACT_GLIDE;
m_turnRate = 0.0f;
SetSequence( SelectWeightedSequence( m_activity ) );
ResetSequenceInfo();
}
//-----------------------------------------------------------------------
void CChicken::Land()
{
if ( m_activity != ACT_LAND || m_vocalizeTimer.IsElapsed() )
{
// throttle interval between vocalizations
m_vocalizeTimer.Start( RandomFloat( 0.5f, 1.0f ) );
EmitSound( "Chicken.Idle" );
}
m_activity = ACT_LAND;
m_turnRate = 0.0f;
SetSequence( SelectWeightedSequence( m_activity ) );
ResetSequenceInfo();
}
//-----------------------------------------------------------------------
void CChicken::Update( void )
{
if ( GetLeader( ) )
return;
if ( !m_updateTimer.IsElapsed() )
return;
m_updateTimer.Start( RandomFloat( 0.5f, 1.0f ) );
// find closest visible player
CUtlVector< CBasePlayer * > playerVector;
CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
float closeRangeSq = FLT_MAX;
CBasePlayer *close = NULL;
for( int i=0; i<playerVector.Count(); ++i )
{
Vector toPlayer = playerVector[i]->GetAbsOrigin() - GetAbsOrigin();
float rangeSq = toPlayer.LengthSqr();
// Only scare chickens if we're close
if ( rangeSq < closeRangeSq )
{
// Only scare chickens if we're moving fast enough to make noise
Vector vPlayerVelocity;
playerVector[i]->GetVelocity( &vPlayerVelocity, NULL );
if ( vPlayerVelocity.Length() > 126.0f )
{
if ( playerVector[i]->IsLineOfSightClear( this ) )
{
closeRangeSq = rangeSq;
close = playerVector[i];
}
}
}
}
const float tooClose = 200.0f;
if ( close && closeRangeSq < tooClose * tooClose )
{
Flee( close, RandomFloat( 0.5, 1.0f ) );
}
}
//-----------------------------------------------------------------------
float CChicken::AvoidObstacles( void )
{
const float feelerRange = 15.0f;
const Vector &forward = Forward();
Vector left( -forward.y, forward.x, 0.0f );
Vector right( forward.y, -forward.x, 0.0f );
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
trace_t resultLeft;
UTIL_TraceLine( WorldSpaceCenter(), WorldSpaceCenter() + feelerRange * ( forward + left ), MASK_PLAYERSOLID, &traceFilter, &resultLeft );
//NDebugOverlay::Line( WorldSpaceCenter(), WorldSpaceCenter() + feelerRange * ( forward + left ), 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
trace_t resultRight;
UTIL_TraceLine( WorldSpaceCenter(), WorldSpaceCenter() + feelerRange * ( forward + right ), MASK_PLAYERSOLID, &traceFilter, &resultRight );
//NDebugOverlay::Line( WorldSpaceCenter(), WorldSpaceCenter() + feelerRange * ( forward + right ), 255, 0, 0, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
const float maxTurnRate = 360.0f;
float turnRate = 0.0f;
if ( resultLeft.DidHit() )
{
if ( resultRight.DidHit() )
{
// both sides hit
if ( resultLeft.fraction < resultRight.fraction )
{
// left hit closer - turn right
turnRate = -maxTurnRate;
}
else
{
// right hit closer - turn left
turnRate = maxTurnRate;
}
}
else
{
// left hit - turn right
turnRate = -maxTurnRate;
}
}
else if ( resultRight.DidHit() )
{
// right hit - turn left
turnRate = maxTurnRate;
}
Vector toAnchor = m_stuckAnchor - GetAbsOrigin();
if ( toAnchor.IsLengthGreaterThan( 50.0f ) )
{
m_stuckAnchor = GetAbsOrigin();
m_stuckTimer.Reset();
}
return turnRate;
}
//-----------------------------------------------------------------------
void CChicken::ResolveCollisions( const Vector &desiredPosition, float deltaT )
{
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
trace_t result;
const float stepHeight = WorldAlignSize().z / 2.0f;
// try to do full move
Vector testPosition = desiredPosition;
for( int slideCount=0; slideCount<3; ++slideCount )
{
UTIL_TraceHull( GetAbsOrigin(), testPosition, WorldAlignMins() + Vector( 0, 0, stepHeight ), WorldAlignMaxs(), MASK_PLAYERSOLID, &traceFilter, &result );
if ( !result.DidHit() )
{
break;
}
Vector fullMove = testPosition - GetAbsOrigin();
float blocked = DotProduct( fullMove, result.plane.normal );
testPosition = GetAbsOrigin() + fullMove - blocked * result.plane.normal;
}
Vector resolvedPosition = result.endpos;
// snap to ground (or fall)
UTIL_TraceHull( resolvedPosition + Vector( 0, 0, stepHeight ), resolvedPosition + Vector( 0, 0, -stepHeight ), WorldAlignMins(), WorldAlignMaxs(), MASK_PLAYERSOLID, &traceFilter, &result );
if ( result.DidHit( ) && !IsJumping( ) )
{
// limit slope that can be walked up
const float slopeLimit = 0.7071f;
if ( !result.plane.normal.IsZero() && result.plane.normal.z > slopeLimit )
{
SetAbsOrigin( result.endpos );
}
else
{
SetAbsOrigin( resolvedPosition );
}
if ( !m_isOnGround )
{
Land();
m_isOnGround = true;
m_bInJump = false;
}
}
else
{
SetAbsOrigin( resolvedPosition );
// fall
SetBaseVelocity( GetBaseVelocity() + Vector( 0, 0, GetGravity() * deltaT ) );
m_isOnGround = false;
Fly();
}
}
void CChicken::UpdateFollowing( float deltaT )
{
if ( !IsFollowingSomeone( ) && m_lastLeaderID != 0 )
{
m_lastLeaderID = 0;
}
// if we have a leader, follow him
CCSPlayer *leader = GetLeader( );
if ( leader )
{
// if leader is dead, stop following him
if ( !leader->IsAlive( ) )
{
Follow( NULL );
Idle( );
return;
}
// m_nHostageState = k_EHostageStates_FollowingPlayer;
// if leader has moved, repath
if ( m_path.IsValid( ) )
{
Vector pathError = leader->GetAbsOrigin( ) - m_path.GetEndpoint( );
const float repathRange = 100.0f;
if ( pathError.IsLengthGreaterThan( repathRange ) )
{
m_path.Invalidate( );
}
// m_activity = ACT_WALK;
}
// build a path to our leader
if ( !m_path.IsValid( ) && m_repathTimer.IsElapsed( ) )
{
const float repathInterval = 0.5f;
m_repathTimer.Start( repathInterval );
Vector from = GetAbsOrigin( );
Vector to = leader->GetAbsOrigin( );
HostagePathCost pathCost;
m_path.Compute( from, to, pathCost );
m_pathFollower.Reset( );
}
// if our rescuer is too far away, give up
const float giveUpRange = 2000.0f;
const float maxPathLength = 4000.0f;
Vector toLeader = leader->GetAbsOrigin( ) - GetAbsOrigin( );
if ( toLeader.IsLengthGreaterThan( giveUpRange ) || ( m_path.IsValid( ) && m_path.GetLength( ) > maxPathLength ) )
{
if ( hostage_debug.GetInt( ) < 2 )
{
Idle( );
}
return;
}
// don't crowd the leader
if ( m_isWaitingForLeader )
{
// we are close to our leader and waiting for him to move
const float waitRange = 200.0f;
if ( toLeader.IsLengthGreaterThan( waitRange ) )
{
// leader has moved away - follow him
m_isWaitingForLeader = false;
}
}
if ( !m_isWaitingForLeader )
{
// move along path towards the leader
m_pathFollower.Update( deltaT, m_inhibitObstacleAvoidanceTimer.IsElapsed( ) );
if ( hostage_debug.GetBool( ) )
{
m_pathFollower.Debug( true );
}
float flDist = GetFeet( ).DistTo( ToCSPlayer( m_leader.m_Value )->GetAbsOrigin( ) );
const float minStuckJumpTime = 1.0f;
if ( ( m_pathFollower.GetStuckDuration( ) > minStuckJumpTime ) && ( flDist > CHICKEN_DISTANCE_RUN * 2 ) )
{
Jump( );
}
if ( hostage_debug.GetBool( ) )
{
m_path.Draw( );
}
}
}
}
//-----------------------------------------------------------------------
void CChicken::ChickenThink( void )
{
float deltaT = gpGlobals->frametime;
SetNextThink( gpGlobals->curtime + deltaT );
// if we've been a zombie for less than CHICKEN_ZOMBIE_SPAWN_DURATION seconds
float flZombieDuration = (gpGlobals->curtime - m_flWhenZombified);
if ( IsZombie() && flZombieDuration < CHICKEN_ZOMBIE_SPAWN_DURATION )
{
m_activity = ACT_CLIMB_UP;
SetSequence( LookupSequence( "spawn" ) );
SetCycle( flZombieDuration / CHICKEN_ZOMBIE_SPAWN_DURATION );
m_activityTimer.Start( 0 );
SetSolid( SOLID_NONE );
}
else if ( IsZombie() && (m_flWhenZombified + 1.0f) > (gpGlobals->curtime - CHICKEN_ZOMBIE_SPAWN_DURATION) )
{
SetSolid( SOLID_BBOX );
m_activity = ACT_WALK;
}
// do leader-following behavior, if necessary
UpdateFollowing( deltaT );
Update();
if ( m_startleTimer.HasStarted() && m_startleTimer.IsElapsed() )
{
// we were startled by something and have just noticed it
m_startleTimer.Invalidate();
Flee( m_fleeFrom, RandomFloat( 2.0f, 3.0f ) );
}
if ( IsActivityFinished() && !IsFollowingSomeone() )
{
switch ( ( int ) m_activity )
{
case ACT_IDLE:
if ( RandomInt( 1, 100 ) < 30 )
{
Walk( );
}
break;
case ACT_WALK:
if ( m_activityTimer.IsElapsed( ) )
{
Idle( );
}
break;
case ACT_RUN:
if ( m_activityTimer.IsElapsed( ) )
{
Walk( );
}
break;
case ACT_GLIDE:
Fly( );
break;
case ACT_LAND:
Walk( );
break;
}
}
else// ongoing activity
{
if ( IsFollowingSomeone( ) )
{
if ( m_moveRateThrottleTimer.IsElapsed( ) )
{
float flDist = GetFeet( ).DistTo( ToCSPlayer( m_leader.m_Value )->GetAbsOrigin( ) );
if ( flDist < ( CHICKEN_DISTANCE_WALK * 0.9f ) )
{
if ( m_activity != ACT_IDLE )
Idle( );
}
else if ( flDist > ( CHICKEN_DISTANCE_WALK * 1.1f ) && flDist < ( CHICKEN_DISTANCE_RUN * 0.9f ) )
{
if ( m_activity != ACT_WALK )
Walk( );
}
if ( flDist > ( CHICKEN_DISTANCE_RUN * 1.1f ) )
{
if ( m_activity != ACT_RUN )
Run( );
}
m_moveRateThrottleTimer.Start( RandomFloat( 0.0f, 0.5f ) );
}
}
if ( m_activity == ACT_IDLE || m_activity == ACT_WALK )
{
// sound like a calm chicken
if ( m_vocalizeTimer.IsElapsed() )
{
m_vocalizeTimer.Start( RandomFloat( 1.5f, 4.5f ) );
EmitSound( "Chicken.Idle" );
}
}
// don't walk/run in a straight line - turn a bit
switch ( ( int ) m_activity )
{
case ACT_WALK:
case ACT_RUN:
QAngle angle = GetAbsAngles( );
if ( m_fleeFrom != NULL )
{
Vector fleeVector = GetAbsOrigin( ) - m_fleeFrom->GetAbsOrigin( );
fleeVector.z = 0.0f;
if ( IsZombie( ) )
fleeVector = -fleeVector; //zombie chickens flee TOWARDS players
float fleeRange = fleeVector.NormalizeInPlace( );
const float safeRange = 150.0f;
if ( fleeRange > safeRange )
{
m_fleeFrom = NULL;
}
else
{
m_activityTimer.Reset( );
if ( DotProduct( fleeVector, Forward( ) ) < 0.7071f )
{
// turn away
m_turnRate = 360.0f;
if ( CrossProduct( fleeVector, Forward( ) ).z > 0.0f )
{
m_turnRate = -m_turnRate;
}
}
else
{
m_turnRate = 0.0f;
}
}
float obstacleTurnRate = AvoidObstacles( );
float actualTurnRate = ( obstacleTurnRate == 0.0f ) ? m_turnRate : obstacleTurnRate;
angle.y += actualTurnRate * deltaT;
SetAbsAngles( angle );
}
else if ( IsFollowingSomeone( ) )
{
Vector followVector = {0,0,0};
m_activityTimer.Reset( );
CTraceFilterNoNPCsOrPlayer traceFilter( this, COLLISION_GROUP_NONE );
trace_t tr;
UTIL_TraceLine( GetEyes( ), ToCSPlayer( m_leader.m_Value )->GetAbsOrigin( ), MASK_PLAYERSOLID, &traceFilter, &tr );
if ( tr.fraction == 1.0 ) // chicken can see player so target player
{
followVector = ToCSPlayer( m_leader.m_Value )->GetAbsOrigin( ) - GetAbsOrigin( );
}
else// chicken cannot see player so target next track node
{
followVector = m_vecPathGoal - GetAbsOrigin( );
}
float followRange = followVector.NormalizeInPlace( );
// we use an softer angle threshold at greater distances. This makes the chicken seem more organic.
float flAngleTheshold = RemapValClamped( followRange, 300, 2000, 0.96, 0.707 );
if ( DotProduct( followVector, Forward( ) ) < flAngleTheshold )
{
m_turnRate = 360.0f;
if ( CrossProduct( followVector, Forward( ) ).z > 0.0f )
{
m_turnRate = -m_turnRate;
}
}
else
{
m_turnRate = 0.0f;
}
// If the path goal is above us then don't try to avoid obstacles even if one is present.
// Instead brute force into a wiggle aka jump;
//
float actualTurnRate = m_turnRate;
float obstacleTurnRate = AvoidObstacles( );
if ( DotProduct( followVector, Up( ) ) >= .9f ) // the path goal is overhead
{
Jump( );
}
if ( obstacleTurnRate != 0.0f )
{
actualTurnRate = obstacleTurnRate;
}
angle.y += actualTurnRate * deltaT;
SetAbsAngles( angle );;
}
break;
}
}
Vector desiredPosition;
Vector velocityFromAnimation = GetGroundSpeedVelocity();
desiredPosition = GetAbsOrigin() + velocityFromAnimation * deltaT;
ResolveCollisions( desiredPosition, deltaT );
SetPlaybackRate( 1.0f );
StudioFrameAdvance();
}
const Vector &CChicken::GetCentroid( void ) const
{
static Vector centroid;
centroid = GetFeet( );
centroid.z += HalfHumanHeight;
return centroid;
}
//-----------------------------------------------------------------------------------------------------
/**
* Return position of "feet" - point below centroid of improv at feet level
*/
const Vector &CChicken::GetFeet( void ) const
{
static Vector feet;
feet = GetAbsOrigin( );
return feet;
}
//-----------------------------------------------------------------------------------------------------
const Vector &CChicken::GetEyes( void ) const
{
static Vector eyes;
eyes = EyePosition( );
return eyes;
}
//-----------------------------------------------------------------------------------------------------
/**
* Return direction of movement
*/
float CChicken::GetMoveAngle( void ) const
{
return GetAbsAngles( ).y;
}
//-----------------------------------------------------------------------------------------------------
CNavArea *CChicken::GetLastKnownArea( void ) const
{
return m_lastKnownArea;
}
//-----------------------------------------------------------------------------------------------------
/**
* Find "simple" ground height, treating current nav area as part of the floo
*/
bool CChicken::GetSimpleGroundHeightWithFloor( const Vector &pos, float *height, Vector *normal )
{
if ( TheNavMesh->GetSimpleGroundHeight( pos, height, normal ) )
{
// our current nav area also serves as a ground polygon
if ( m_lastKnownArea && m_lastKnownArea->IsOverlapping( pos ) )
{
*height = MAX( ( *height ), m_lastKnownArea->GetZ( pos ) );
}
return true;
}
return false;
}
//-----------------------------------------------------------------------------------------------------
void CChicken::Crouch( void )
{
// m_isCrouching = true;
}
//-----------------------------------------------------------------------------------------------------
/**
* un-crouch
*/
void CChicken::StandUp( void )
{
// m_isCrouching = false;
}
//-----------------------------------------------------------------------------------------------------
bool CChicken::IsCrouching( void ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------------
/**
* Initiate a jump
*/
void CChicken::Jump( void )
{
float flJumpPower = 250.0f; /*RandomFloat( 10.0f, 200.0f );*/ /*RemapValClamped( flTimeSinceLastJump, 3.0, 20.0, 200.0f, 30.0f );*/
Jump( flJumpPower );
}
void CChicken::Jump( float flVelocity )
{
// don't jump if the nav disallows it
CNavArea *myArea = GetLastKnownArea( );
if ( myArea && myArea->HasAttributes( NAV_MESH_NO_JUMP ) )
return;
if ( CanJump( ) && IsOnGround( ) )
{
const float minJumpInterval = 0.1f;
m_jumpTimer.Start( minJumpInterval );
m_bInJump = true;
//
// Vector dir;
// AngleVectors( GetAbsAngles( ), &dir, NULL, NULL );
//
// vel += dir * 10;
Vector vel = GetAbsVelocity( );
vel.z += flVelocity;
SetAbsVelocity( vel );
SetSequence( SelectWeightedSequence( ACT_RUN ) );
ResetSequenceInfo( );
m_jumpedThisFrame = true;
m_flLastJumpTime = gpGlobals->curtime;
}
}
//-----------------------------------------------------------------------------------------------------
bool CChicken::IsJumping( void ) const
{
//return m_bInJump;
return !m_jumpTimer.IsElapsed( );
}
bool CChicken::CanJump( void ) const
{
return !IsJumping( ) && ( gpGlobals->curtime - m_flLastJumpTime > 3.0f );
}
//-----------------------------------------------------------------------------------------------------
/**
* Set movement speed to running
*/
void CChicken::Run( void )
{
// m_isRunning = true;
m_activity = ACT_RUN;
m_activityTimer.Start( RandomFloat( 0.5, 3.0f ) );
m_turnRate = RandomFloat( -45.0f, 45.0f );
SetSequence( SelectWeightedSequence( m_activity ) );
ResetSequenceInfo( );
}
// -----------------------------------------------------------------------------------------------------
// /**
// * Set movement speed to walking
// */
// void CChicken::Walk( void )
// {
// m_isRunning = false;
// }
//-----------------------------------------------------------------------------------------------------
bool CChicken::IsRunning( void ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------------
/**
* Invoked when a ladder is encountered while following a path
*/
void CChicken::StartLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos )
{
}
//-----------------------------------------------------------------------------------------------------
/**
* Traverse given ladder
*/
bool CChicken::TraverseLadder( const CNavLadder *ladder, NavTraverseType how, const Vector &approachPos, const Vector &departPos, float deltaT )
{
return false;
}
//-----------------------------------------------------------------------------------------------------
bool CChicken::IsUsingLadder( void ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------------
void CChicken::TrackPath( const Vector &pathGoal, float deltaT )
{
m_vecPathGoal = pathGoal;
}
//-----------------------------------------------------------------------------------------------------
/**
* Invoked when an improv reaches its MoveTo goal
*/
void CChicken::OnMoveToSuccess( const Vector &goal )
{
}
//-----------------------------------------------------------------------------------------------------
/**
* Invoked when an improv fails to reach a MoveTo goal
*/
void CChicken::OnMoveToFailure( const Vector &goal, MoveToFailureType reason )
{
}