diff options
Diffstat (limited to 'game/server/hl1/hl1_npc_leech.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_leech.cpp | 724 |
1 files changed, 724 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_leech.cpp b/game/server/hl1/hl1_npc_leech.cpp new file mode 100644 index 0000000..9e00fc0 --- /dev/null +++ b/game/server/hl1/hl1_npc_leech.cpp @@ -0,0 +1,724 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "ai_default.h" +#include "ai_task.h" +#include "ai_schedule.h" +#include "ai_node.h" +#include "ai_hull.h" +#include "ai_hint.h" +#include "ai_memory.h" +#include "ai_route.h" +#include "ai_motor.h" +#include "soundent.h" +#include "game.h" +#include "npcevent.h" +#include "entitylist.h" +#include "activitylist.h" +#include "animation.h" +#include "basecombatweapon.h" +#include "IEffects.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "ammodef.h" +#include "hl1_ai_basenpc.h" +#include "ai_senses.h" + +// Animation events +#define LEECH_AE_ATTACK 1 +#define LEECH_AE_FLOP 2 + +//#define DEBUG_BEAMS 0 + +ConVar sk_leech_health( "sk_leech_health", "2" ); +ConVar sk_leech_dmg_bite( "sk_leech_dmg_bite", "2" ); + +// Movement constants + +#define LEECH_ACCELERATE 10 +#define LEECH_CHECK_DIST 45 +#define LEECH_SWIM_SPEED 50 +#define LEECH_SWIM_ACCEL 80 +#define LEECH_SWIM_DECEL 10 +#define LEECH_TURN_RATE 70 +#define LEECH_SIZEX 10 +#define LEECH_FRAMETIME 0.1 + +class CNPC_Leech : public CHL1BaseNPC +{ + DECLARE_CLASS( CNPC_Leech, CHL1BaseNPC ); +public: + + DECLARE_DATADESC(); + + void Spawn( void ); + void Precache( void ); + + static const char *pAlertSounds[]; + + void SwimThink( void ); + void DeadThink( void ); + + void SwitchLeechState( void ); + float ObstacleDistance( CBaseEntity *pTarget ); + void UpdateMotion( void ); + + void RecalculateWaterlevel( void ); + void Touch( CBaseEntity *pOther ); + + Disposition_t IRelationType(CBaseEntity *pTarget); + + void HandleAnimEvent( animevent_t *pEvent ); + + void AttackSound( void ); + void AlertSound( void ); + + void Activate( void ); + + Class_T Classify( void ) { return CLASS_INSECT; }; + + void Event_Killed( const CTakeDamageInfo &info ); + + + bool ShouldGib( const CTakeDamageInfo &info ); + + +/* // Base entity functions + void Killed( entvars_t *pevAttacker, int iGib ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +*/ + +private: + // UNDONE: Remove unused boid vars, do group behavior + float m_flTurning;// is this boid turning? + bool m_fPathBlocked;// TRUE if there is an obstacle ahead + float m_flAccelerate; + float m_obstacle; + float m_top; + float m_bottom; + float m_height; + float m_waterTime; + float m_sideTime; // Timer to randomly check clearance on sides + float m_zTime; + float m_stateTime; + float m_attackSoundTime; + Vector m_oldOrigin; +}; + +LINK_ENTITY_TO_CLASS( monster_leech, CNPC_Leech ); + +BEGIN_DATADESC( CNPC_Leech ) + DEFINE_FIELD( m_flTurning, FIELD_FLOAT ), + DEFINE_FIELD( m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flAccelerate, FIELD_FLOAT ), + DEFINE_FIELD( m_obstacle, FIELD_FLOAT ), + DEFINE_FIELD( m_top, FIELD_FLOAT ), + DEFINE_FIELD( m_bottom, FIELD_FLOAT ), + DEFINE_FIELD( m_height, FIELD_FLOAT ), + DEFINE_FIELD( m_waterTime, FIELD_TIME ), + DEFINE_FIELD( m_sideTime, FIELD_TIME ), + DEFINE_FIELD( m_zTime, FIELD_TIME ), + DEFINE_FIELD( m_stateTime, FIELD_TIME ), + DEFINE_FIELD( m_attackSoundTime, FIELD_TIME ), + DEFINE_FIELD( m_oldOrigin, FIELD_VECTOR ), + + DEFINE_THINKFUNC( SwimThink ), + DEFINE_THINKFUNC( DeadThink ), +END_DATADESC() + + +bool CNPC_Leech::ShouldGib( const CTakeDamageInfo &info ) +{ + return false; +} + +void CNPC_Leech::Spawn( void ) +{ + Precache(); + SetModel( "models/leech.mdl" ); + + SetHullType(HULL_TINY_CENTERED); + SetHullSizeNormal(); + + UTIL_SetSize( this, Vector(-1,-1,0), Vector(1,1,2)); + + Vector vecSurroundingMins(-8,-8,0); + Vector vecSurroundingMaxs(8,8,2); + CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs ); + + // Don't push the minz down too much or the water check will fail because this entity is really point-sized + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_FLY ); + AddFlag( FL_SWIM ); + m_iHealth = sk_leech_health.GetInt(); + + m_flFieldOfView = -0.5; // 180 degree FOV + SetDistLook( 750 ); + NPCInit(); + SetThink( &CNPC_Leech::SwimThink ); + SetUse( NULL ); + SetTouch( NULL ); + SetViewOffset( vec3_origin ); + + m_flTurning = 0; + m_fPathBlocked = FALSE; + SetActivity( ACT_SWIM ); + SetState( NPC_STATE_IDLE ); + m_stateTime = gpGlobals->curtime + random->RandomFloat( 1, 5 ); + + SetRenderColor( 255, 255, 255, 255 ); + + m_bloodColor = DONT_BLEED; + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); +} + +void CNPC_Leech::Activate( void ) +{ + RecalculateWaterlevel(); + + BaseClass::Activate(); +} + +void CNPC_Leech::DeadThink( void ) +{ + if ( IsSequenceFinished() ) + { + if ( GetActivity() == ACT_DIEFORWARD ) + { + SetThink( NULL ); + StopAnimation(); + return; + } + else if ( GetFlags() & FL_ONGROUND ) + { + AddSolidFlags( FSOLID_NOT_SOLID ); + SetActivity( ACT_DIEFORWARD ); + } + } + StudioFrameAdvance(); + SetNextThink( gpGlobals->curtime + 0.1 ); + + // Apply damage velocity, but keep out of the walls + if ( GetAbsVelocity().x != 0 || GetAbsVelocity().y != 0 ) + { + trace_t tr; + + // Look 0.5 seconds ahead + UTIL_TraceLine( GetLocalOrigin(), GetLocalOrigin() + GetAbsVelocity() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); + if (tr.fraction != 1.0) + { + Vector vVelocity = GetAbsVelocity(); + + vVelocity.x = 0; + vVelocity.y = 0; + + SetAbsVelocity( vVelocity ); + } + } +} + + +Disposition_t CNPC_Leech::IRelationType( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + return D_HT; + + return BaseClass::IRelationType( pTarget ); +} + +void CNPC_Leech::Touch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + if ( pOther == GetTouchTrace().m_pEnt ) + { + if ( pOther->GetAbsVelocity() == vec3_origin ) + return; + + SetBaseVelocity( pOther->GetAbsVelocity() ); + AddFlag( FL_BASEVELOCITY ); + } +} + +void CNPC_Leech::HandleAnimEvent( animevent_t *pEvent ) +{ + CBaseEntity *pEnemy = GetEnemy(); + + switch( pEvent->event ) + { + case LEECH_AE_FLOP: + // Play flop sound + break; + + case LEECH_AE_ATTACK: + AttackSound(); + + if ( pEnemy != NULL ) + { + Vector dir, face; + + AngleVectors( GetAbsAngles(), &face ); + + face.z = 0; + dir = (pEnemy->GetLocalOrigin() - GetLocalOrigin() ); + dir.z = 0; + + VectorNormalize( dir ); + VectorNormalize( face ); + + if ( DotProduct(dir, face) > 0.9 ) // Only take damage if the leech is facing the prey + { + CTakeDamageInfo info( this, this, sk_leech_dmg_bite.GetInt(), DMG_SLASH ); + CalculateMeleeDamageForce( &info, dir, pEnemy->GetAbsOrigin() ); + pEnemy->TakeDamage( info ); + } + } + m_stateTime -= 2; + break; + + + + default: + BaseClass::HandleAnimEvent( pEvent ); + break; + } +} + + +void CNPC_Leech::Precache( void ) +{ + PrecacheModel("models/leech.mdl"); + + PrecacheScriptSound( "Leech.Attack" ); + PrecacheScriptSound( "Leech.Alert" ); +} + + +void CNPC_Leech::AttackSound( void ) +{ + if ( gpGlobals->curtime > m_attackSoundTime ) + { + CPASAttenuationFilter filter( this ); + + EmitSound(filter, entindex(), "Leech.Attack" ); + m_attackSoundTime = gpGlobals->curtime + 0.5; + } +} + + +void CNPC_Leech::AlertSound( void ) +{ + CPASAttenuationFilter filter( this ); + EmitSound(filter, entindex(), "Leech.Alert" ); +} + +void CNPC_Leech::SwitchLeechState( void ) +{ + m_stateTime = gpGlobals->curtime + random->RandomFloat( 3, 6 ); + if ( m_NPCState == NPC_STATE_COMBAT ) + { + SetEnemy ( NULL ); + SetState( NPC_STATE_IDLE ); + // We may be up against the player, so redo the side checks + m_sideTime = 0; + } + else + { + GetSenses()->Look( GetSenses()->GetDistLook() ); + CBaseEntity *pEnemy = BestEnemy(); + if ( pEnemy && pEnemy->GetWaterLevel() != 0 ) + { + SetEnemy ( pEnemy ); + SetState( NPC_STATE_COMBAT ); + m_stateTime = gpGlobals->curtime + random->RandomFloat( 18, 25 ); + AlertSound(); + } + } +} + +void CNPC_Leech::RecalculateWaterlevel( void ) +{ + // Calculate boundaries + Vector vecTest = GetLocalOrigin() - Vector(0,0,400); + + trace_t tr; + + UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); + + if ( tr.fraction != 1.0 ) + m_bottom = tr.endpos.z + 1; + else + m_bottom = vecTest.z; + + m_top = UTIL_WaterLevel( GetLocalOrigin(), GetLocalOrigin().z, GetLocalOrigin().z + 400 ) - 1; + +#if DEBUG_BEAMS + NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + Vector( 0, 0, m_bottom ), 0, 255, 0, false, 0.1f ); + NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + Vector( 0, 0, m_top ), 0, 255, 255, false, 0.1f ); +#endif + + // Chop off 20% of the outside range + float newBottom = m_bottom * 0.8 + m_top * 0.2; + m_top = m_bottom * 0.2 + m_top * 0.8; + m_bottom = newBottom; + m_height = random->RandomFloat( m_bottom, m_top ); + m_waterTime = gpGlobals->curtime + random->RandomFloat( 5, 7 ); +} + +void CNPC_Leech::SwimThink( void ) +{ + trace_t tr; + float flLeftSide; + float flRightSide; + float targetSpeed; + float targetYaw = 0; + CBaseEntity *pTarget; + + /*if ( !UTIL_FindClientInPVS( edict() ) ) + { + m_flNextThink = gpGlobals->curtime + random->RandomFloat( 1.0f, 1.5f ); + SetAbsVelocity( vec3_origin ); + return; + } + else*/ + SetNextThink( gpGlobals->curtime + 0.1 ); + + targetSpeed = LEECH_SWIM_SPEED; + + if ( m_waterTime < gpGlobals->curtime ) + RecalculateWaterlevel(); + + if ( m_stateTime < gpGlobals->curtime ) + SwitchLeechState(); + + ClearCondition( COND_CAN_MELEE_ATTACK1 ); + + switch( m_NPCState ) + { + case NPC_STATE_COMBAT: + pTarget = GetEnemy(); + if ( !pTarget ) + SwitchLeechState(); + else + { + // Chase the enemy's eyes + m_height = pTarget->GetLocalOrigin().z + pTarget->GetViewOffset().z - 5; + // Clip to viable water area + if ( m_height < m_bottom ) + m_height = m_bottom; + else if ( m_height > m_top ) + m_height = m_top; + Vector location = pTarget->GetLocalOrigin() - GetLocalOrigin(); + location.z += (pTarget->GetViewOffset().z); + if ( location.Length() < 80 ) + SetCondition( COND_CAN_MELEE_ATTACK1 ); + // Turn towards target ent + targetYaw = UTIL_VecToYaw( location ); + + QAngle vTestAngle = GetAbsAngles(); + + targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( GetAbsAngles().y ) ); + + if ( targetYaw < (-LEECH_TURN_RATE) ) + targetYaw = (-LEECH_TURN_RATE); + else if ( targetYaw > (LEECH_TURN_RATE) ) + targetYaw = (LEECH_TURN_RATE); + else + targetSpeed *= 2; + } + + break; + + default: + if ( m_zTime < gpGlobals->curtime ) + { + float newHeight = random->RandomFloat( m_bottom, m_top ); + m_height = 0.5 * m_height + 0.5 * newHeight; + m_zTime = gpGlobals->curtime + random->RandomFloat( 1, 4 ); + } + if ( random->RandomInt( 0, 100 ) < 10 ) + targetYaw = random->RandomInt( -30, 30 ); + pTarget = NULL; + // oldorigin test + if ( ( GetLocalOrigin() - m_oldOrigin ).Length() < 1 ) + { + // If leech didn't move, there must be something blocking it, so try to turn + m_sideTime = 0; + } + + break; + } + + m_obstacle = ObstacleDistance( pTarget ); + m_oldOrigin = GetLocalOrigin(); + if ( m_obstacle < 0.1 ) + m_obstacle = 0.1; + + Vector vForward, vRight; + + AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL ); + + // is the way ahead clear? + if ( m_obstacle == 1.0 ) + { + // if the leech is turning, stop the trend. + if ( m_flTurning != 0 ) + { + m_flTurning = 0; + } + + m_fPathBlocked = FALSE; + m_flSpeed = UTIL_Approach( targetSpeed, m_flSpeed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME ); + SetAbsVelocity( vForward * m_flSpeed ); + + } + else + { + m_obstacle = 1.0 / m_obstacle; + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid + { + Vector vecTest; + // measure clearance on left and right to pick the best dir to turn + vecTest = GetLocalOrigin() + ( vRight * LEECH_SIZEX) + ( vForward * LEECH_CHECK_DIST); + UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); + flRightSide = tr.fraction; + + vecTest = GetLocalOrigin() + ( vRight * -LEECH_SIZEX) + ( vForward * LEECH_CHECK_DIST); + UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); + + flLeftSide = tr.fraction; + + // turn left, right or random depending on clearance ratio + float delta = (flRightSide - flLeftSide); + if ( delta > 0.1 || (delta > -0.1 && random->RandomInt( 0,100 ) < 50 ) ) + m_flTurning = -LEECH_TURN_RATE; + else + m_flTurning = LEECH_TURN_RATE; + } + + m_flSpeed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), m_flSpeed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle ); + SetAbsVelocity( vForward * m_flSpeed ); + } + + GetMotor()->SetIdealYaw( m_flTurning + targetYaw ); + UpdateMotion(); +} + +// +// ObstacleDistance - returns normalized distance to obstacle +// +float CNPC_Leech::ObstacleDistance( CBaseEntity *pTarget ) +{ + trace_t tr; + Vector vecTest; + Vector vForward, vRight; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //Vector vecDir = UTIL_VecToAngles( pev->velocity ); + QAngle tmp = GetAbsAngles(); + tmp.x = -tmp.x; + AngleVectors ( tmp, &vForward, &vRight, NULL ); + + // check for obstacle ahead + vecTest = GetLocalOrigin() + vForward * LEECH_CHECK_DIST; + UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); + + if ( tr.startsolid ) + { + m_flSpeed = -LEECH_SWIM_SPEED * 0.5; + } + + if ( tr.fraction != 1.0 ) + { + if ( (pTarget == NULL || tr.m_pEnt != pTarget ) ) + { + return tr.fraction; + } + else + { + if ( fabs( m_height - GetLocalOrigin().z ) > 10 ) + return tr.fraction; + } + } + + if ( m_sideTime < gpGlobals->curtime ) + { + // extra wide checks + vecTest = GetLocalOrigin() + vRight * LEECH_SIZEX * 2 + vForward * LEECH_CHECK_DIST; + UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); + + if (tr.fraction != 1.0) + return tr.fraction; + + vecTest = GetLocalOrigin() - vRight * LEECH_SIZEX * 2 + vForward * LEECH_CHECK_DIST; + UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); + if (tr.fraction != 1.0) + return tr.fraction; + + // Didn't hit either side, so stop testing for another 0.5 - 1 seconds + m_sideTime = gpGlobals->curtime + random->RandomFloat(0.5,1); + } + + return 1.0; +} + +void CNPC_Leech::UpdateMotion( void ) +{ + float flapspeed = ( m_flSpeed - m_flAccelerate) / LEECH_ACCELERATE; + m_flAccelerate = m_flAccelerate * 0.8 + m_flSpeed * 0.2; + + if (flapspeed < 0) + flapspeed = -flapspeed; + flapspeed += 1.0; + if (flapspeed < 0.5) + flapspeed = 0.5; + if (flapspeed > 1.9) + flapspeed = 1.9; + + m_flPlaybackRate = flapspeed; + + QAngle vAngularVelocity = GetLocalAngularVelocity(); + QAngle vAngles = GetLocalAngles(); + + if ( !m_fPathBlocked ) + vAngularVelocity.y = GetMotor()->GetIdealYaw(); + else + vAngularVelocity.y = GetMotor()->GetIdealYaw() * m_obstacle; + + if ( vAngularVelocity.y > 150 ) + SetIdealActivity( ACT_TURN_LEFT ); + else if ( vAngularVelocity.y < -150 ) + SetIdealActivity( ACT_TURN_RIGHT ); + else + SetIdealActivity( ACT_SWIM ); + + // lean + float targetPitch, delta; + delta = m_height - GetLocalOrigin().z; + +/* if ( delta < -10 ) + targetPitch = -30; + else if ( delta > 10 ) + targetPitch = 30; + else*/ + targetPitch = 0; + + vAngles.x = UTIL_Approach( targetPitch, vAngles.x, 60 * LEECH_FRAMETIME ); + + // bank + vAngularVelocity.z = - ( vAngles.z + (vAngularVelocity.y * 0.25)); + + if ( m_NPCState == NPC_STATE_COMBAT && HasCondition( COND_CAN_MELEE_ATTACK1 ) ) + SetIdealActivity( ACT_MELEE_ATTACK1 ); + + // Out of water check + if ( !GetWaterLevel() ) + { + SetMoveType( MOVETYPE_FLYGRAVITY ); + SetIdealActivity( ACT_HOP ); + SetAbsVelocity( vec3_origin ); + + // Animation will intersect the floor if either of these is non-zero + vAngles.z = 0; + vAngles.x = 0; + + m_flPlaybackRate = random->RandomFloat( 0.8, 1.2 ); + } + else if ( GetMoveType() == MOVETYPE_FLYGRAVITY ) + { + SetMoveType( MOVETYPE_FLY ); + SetGroundEntity( NULL ); + + // TODO + RecalculateWaterlevel(); + m_waterTime = gpGlobals->curtime + 2; // Recalc again soon, water may be rising + } + + if ( GetActivity() != GetIdealActivity() ) + { + SetActivity ( GetIdealActivity() ); + } + StudioFrameAdvance(); + + DispatchAnimEvents ( this ); + + SetLocalAngles( vAngles ); + SetLocalAngularVelocity( vAngularVelocity ); + + Vector vForward, vRight; + + AngleVectors( vAngles, &vForward, &vRight, NULL ); + +#if DEBUG_BEAMS + if ( m_fPathBlocked ) + { + float color = m_obstacle * 30; + if ( m_obstacle == 1.0 ) + color = 0; + if ( color > 255 ) + color = 255; + NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vForward * LEECH_CHECK_DIST, 255, color, color, false, 0.1f ); + } + else + NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vForward * LEECH_CHECK_DIST, 255, 255, 0, false, 0.1f ); + + NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vRight * (vAngularVelocity.y*0.25), 0, 0, 255, false, 0.1f ); +#endif + +} + +void CNPC_Leech::Event_Killed( const CTakeDamageInfo &info ) +{ + Vector vecSplatDir; + trace_t tr; + + //ALERT(at_aiconsole, "Leech: killed\n"); + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = GetOwnerEntity(); + if (pOwner) + pOwner->DeathNotice( this ); + + // When we hit the ground, play the "death_end" activity + if ( GetWaterLevel() ) + { + QAngle qAngles = GetAbsAngles(); + QAngle qAngularVel = GetLocalAngularVelocity(); + Vector vOrigin = GetLocalOrigin(); + + qAngles.z = 0; + qAngles.x = 0; + + vOrigin.z += 1; + + SetAbsVelocity( vec3_origin ); + + if ( random->RandomInt( 0, 99 ) < 70 ) + qAngularVel.y = random->RandomInt( -720, 720 ); + + SetAbsAngles( qAngles ); + SetLocalAngularVelocity( qAngularVel ); + SetAbsOrigin( vOrigin ); + + + SetGravity ( 0.02 ); + SetGroundEntity( NULL ); + SetActivity( ACT_DIESIMPLE ); + } + else + SetActivity( ACT_DIEFORWARD ); + + SetMoveType( MOVETYPE_FLYGRAVITY ); + m_takedamage = DAMAGE_NO; + + SetThink( &CNPC_Leech::DeadThink ); +} |