Files
HL2Overcharged/game/server/overcharged/npc_bullsquid.cpp
2025-05-21 21:20:08 +03:00

1270 lines
34 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements the bullsquid
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "game.h"
#include "AI_Default.h"
#include "AI_Schedule.h"
#include "AI_Hull.h"
#include "AI_Navigator.h"
#include "AI_Motor.h"
#include "AI_Squad.h"
#include "npc_bullsquid.h"
#include "NPCEvent.h"
#include "soundent.h"
#include "activitylist.h"
#include "weapon_brickbat.h"
#include "npc_headcrab.h"
#include "player.h"
#include "gamerules.h" // For g_pGameRules
#include "ammodef.h"
#include "grenade_bullsquid.h"
#include "grenade_brickbat.h"
#include "entitylist.h"
#include "shake.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"
#include "AI_Hint.h"
#include "AI_Senses.h"
#include "BloodDripsGreen.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve
ConVar sk_bullsquid_health( "sk_bullsquid_health", "0" );
ConVar sk_bullsquid_dmg_bite( "sk_bullsquid_dmg_bite", "0" );
ConVar sk_bullsquid_dmg_whip( "sk_bullsquid_dmg_whip", "0" );
//=========================================================
// monster-specific schedule types
//=========================================================
enum
{
SCHED_SQUID_HURTHOP = LAST_SHARED_SCHEDULE + 1,
SCHED_SQUID_SEECRAB,
SCHED_SQUID_EAT,
SCHED_SQUID_SNIFF_AND_EAT,
SCHED_SQUID_WALLOW,
};
//=========================================================
// monster-specific tasks
//=========================================================
enum
{
TASK_SQUID_HOPTURN = LAST_SHARED_TASK + 1,
TASK_SQUID_EAT,
};
//-----------------------------------------------------------------------------
// Squid Conditions
//-----------------------------------------------------------------------------
enum
{
COND_SQUID_SMELL_FOOD = LAST_SHARED_CONDITION + 1,
};
//=========================================================
// Interactions
//=========================================================
int g_interactionBullsquidThrow = 0;
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define BSQUID_AE_SPIT ( 1 )
#define BSQUID_AE_BITE ( 2 )
#define BSQUID_AE_BLINK ( 3 )
//#define BSQUID_AE_ROAR ( 4 ) // Light Kill : Valve you forget this function
#define BSQUID_AE_TAILWHIP ( 4 )// Light Kill : Now time for true attack
#define BSQUID_AE_HOP ( 5 )
#define BSQUID_AE_THROW ( 6 )
//#define BSQUID_AE_WHIP_SND ( 7 )
LINK_ENTITY_TO_CLASS( npc_bullsquid, CNPC_Bullsquid );
int ACT_SQUID_EXCITED;
int ACT_SQUID_EAT;
int ACT_SQUID_DETECT_SCENT;
int ACT_SQUID_INSPECT_FLOOR;
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_Bullsquid )
DEFINE_FIELD( m_fCanThreatDisplay, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flLastHurtTime, FIELD_TIME ),
DEFINE_FIELD( m_flNextSpitTime, FIELD_TIME ),
// DEFINE_FIELD( m_nSquidSpitSprite, FIELD_INTEGER ),
DEFINE_FIELD( m_flHungryTime, FIELD_TIME ),
DEFINE_FIELD( m_nextSquidSoundTime, FIELD_TIME ),
END_DATADESC()
//=========================================================
// Spawn
//=========================================================
void CNPC_Bullsquid::Spawn()
{
Precache( );
SetModel( "models/bullsquid.mdl");
SetHullType(HULL_WIDE_SHORT);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
m_bloodColor = BLOOD_COLOR_GREEN;
SetCollisionGroup(COLLISION_GROUP_NPC);
SetRenderColor( 255, 255, 255, 255 );
m_iHealth = sk_bullsquid_health.GetFloat();
m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
m_NPCState = NPC_STATE_NONE;
CapabilitiesClear();
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 );
CapabilitiesAdd(bits_CAP_SQUAD);
m_fCanThreatDisplay = TRUE;
m_flNextSpitTime = gpGlobals->curtime;
NPCInit();
m_flDistTooFar = 784;
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Bullsquid::Precache()
{
PrecacheModel( "models/bullsquid.mdl" );
//m_nSquidSpitSprite = PrecacheModel("sprites/greenspit1.vmt");// client side spittle.
UTIL_PrecacheOther( "grenade_spit" );
PrecacheScriptSound( "NPC_Bullsquid.Idle" );
PrecacheScriptSound( "NPC_Bullsquid.Pain" );
PrecacheScriptSound( "NPC_Bullsquid.Alert" );
PrecacheScriptSound( "NPC_Bullsquid.Death" );
PrecacheScriptSound( "NPC_Bullsquid.Attack1" );
PrecacheScriptSound( "NPC_Bullsquid.Growl" );
PrecacheScriptSound( "NPC_Bullsquid.TailWhip");
PrecacheScriptSound( "NPC_Antlion.PoisonBall");
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose: Indicates this monster's place in the relationship table.
// Output :
//-----------------------------------------------------------------------------
Class_T CNPC_Bullsquid::Classify( void )
{
return CLASS_BULLSQUID;
}
//=========================================================
// IdleSound
//=========================================================
#define SQUID_ATTN_IDLE (float)1.5
void CNPC_Bullsquid::IdleSound( void )
{
EmitSound( "NPC_Bullsquid.Idle" );
}
//=========================================================
// PainSound
//=========================================================
void CNPC_Bullsquid::PainSound( const CTakeDamageInfo &info )
{
EmitSound( "NPC_Bullsquid.Pain" );
}
//=========================================================
// AlertSound
//=========================================================
void CNPC_Bullsquid::AlertSound( void )
{
EmitSound( "NPC_Bullsquid.Alert" );
}
//=========================================================
// DeathSound
//=========================================================
void CNPC_Bullsquid::DeathSound( const CTakeDamageInfo &info )
{
EmitSound( "NPC_Bullsquid.Death" );
}
//=========================================================
// AttackSound
//=========================================================
void CNPC_Bullsquid::AttackSound( void )
{
EmitSound( "NPC_Bullsquid.Attack1" );
}
//=========================================================
// GrowlSound
//=========================================================
void CNPC_Bullsquid::GrowlSound( void )
{
if (gpGlobals->curtime >= m_nextSquidSoundTime)
{
EmitSound( "NPC_Bullsquid.Growl" );
m_nextSquidSoundTime = gpGlobals->curtime + random->RandomInt(1.5,3.0);
}
}
//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
float CNPC_Bullsquid::MaxYawSpeed( void )
{
float flYS = 0;
switch ( GetActivity() )
{
case ACT_WALK: flYS = 90; break;
case ACT_RUN: flYS = 90; break;
case ACT_IDLE: flYS = 90; break;
case ACT_RANGE_ATTACK1: flYS = 90; break;
default:
flYS = 90;
break;
}
return flYS;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Bullsquid::HandleAnimEvent( animevent_t *pEvent )
{
switch( pEvent->event )
{
case BSQUID_AE_SPIT:
{
if ( GetEnemy() )
{
/*Vector vSpitPos;
GetAttachment( "Mouth", vSpitPos );
Vector vTarget = GetEnemy()->GetAbsOrigin();
Vector vToss;
CBaseEntity* pBlocker;
float flGravity = SPIT_GRAVITY;
ThrowLimit(vSpitPos, vTarget, flGravity, 3, Vector(0,0,0), Vector(0,0,0), GetEnemy(), &vToss, &pBlocker);
CGrenadeSpit *pGrenade = (CGrenadeSpit*)CreateNoSpawn( "grenade_spit", vSpitPos, vec3_angle, this );
//pGrenade->KeyValue( "velocity", vToss );
pGrenade->Spawn( );
pGrenade->SetThrower( this );
pGrenade->SetOwnerEntity( this );
pGrenade->SetSpitSize( 2 );
pGrenade->SetAbsVelocity( vToss );
// Tumble through the air
pGrenade->SetLocalAngularVelocity(
QAngle(
random->RandomFloat( -100, -500 ),
random->RandomFloat( -100, -500 ),
random->RandomFloat( -100, -500 )
)
);
AttackSound();
CPVSFilter filter( vSpitPos );
te->SpriteSpray( filter, 0.0,
&vSpitPos, &vToss, m_nSquidSpitSprite, 5, 10, 15 );*/
// Light Kill : New spit form.
Vector vSpitPos;
GetAttachment( "mouth", vSpitPos );
Vector vTarget;
vSpitPos.y += 0.0;
vSpitPos.x += 0.0;
vSpitPos.z += 15.0;
// If our enemy is looking at us and far enough away, lead him
if ( HasCondition( COND_ENEMY_FACING_ME ) && UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() ) > (40*12) )
{
UTIL_PredictedPosition( GetEnemy(), 0.5f, &vTarget );
vTarget.z = GetEnemy()->GetAbsOrigin().z;
}
else
{
// Otherwise he can't see us and he won't be able to dodge
vTarget = GetEnemy()->BodyTarget( vSpitPos, true );
}
vTarget[2] += random->RandomFloat( 0.0f, 32.0f );
// Try and spit at our target
Vector vecToss;
if ( GetSpitVector( vSpitPos, vTarget, &vecToss ) == false )
{
//DevMsg("GetSpitVector( vSpitPos, vTarget, &vecToss ) == false\n");
// Now try where they were
if ( GetSpitVector( vSpitPos, m_vSavePosition, &vecToss ) == false )
{
//DevMsg("GetSpitVector( vSpitPos, m_vSavePosition, &vecToss ) == false\n");
// Failing that, just shoot with the old velocity we calculated initially!
vecToss = m_vecSaveSpitVelocity;
}
}
// Find what our vertical theta is to estimate the time we'll impact the ground
Vector vecToTarget = ( vTarget - vSpitPos );
VectorNormalize( vecToTarget );
float flVelocity = VectorNormalize( vecToss );
float flCosTheta = DotProduct( vecToTarget, vecToss );
float flTime = (vSpitPos-vTarget).Length2D() / ( flVelocity * flCosTheta );
// Emit a sound where this is going to hit so that targets get a chance to act correctly
CSoundEnt::InsertSound( SOUND_DANGER, vTarget, (15*12), flTime, this );
// Don't fire again until this volley would have hit the ground (with some lag behind it)
SetNextAttack( gpGlobals->curtime + flTime + random->RandomFloat( 0.5f, 2.0f ) );
int idx = random->RandomInt(5, 12);
for (int i = 0; i < idx; i++)
{
CGrenadeBullsquid *pGrenade = (CGrenadeBullsquid*) CreateEntityByName( "grenade_bullsquid" );
pGrenade->SetCanMakeDecal(i);
pGrenade->SetAbsOrigin( vSpitPos );
pGrenade->SetAbsAngles( vec3_angle );
DispatchSpawn( pGrenade );
pGrenade->SetThrower( this );
pGrenade->SetOwnerEntity( this );
if ( i == 0 )
{
#ifdef HL2_EPISODIC//OverCharged
pGrenade->SetSpitSize( SPIT_LARGE );
#endif
pGrenade->SetAbsVelocity( vecToss * flVelocity );
}
else
{
pGrenade->SetAbsVelocity( ( vecToss + RandomVector( -0.065f, 0.065f ) ) * flVelocity );
#ifdef HL2_EPISODIC//OverCharged
pGrenade->SetSpitSize( random->RandomInt( SPIT_SMALL, SPIT_MEDIUM ) );
#endif
}
// Tumble through the air
pGrenade->SetLocalAngularVelocity(QAngle( random->RandomFloat( -250, -500 ),random->RandomFloat( -250, -500 ),random->RandomFloat( -250, -500 ) ) );
}
//EmitSound( "NPC_Bullsquid.PoisonShoot" );
AttackSound();
}//end
}
break;
case BSQUID_AE_BITE:
{
CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_bite.GetFloat(), DMG_SLASH );
if ( pHurt )
{
/*Vector forward, up;
AngleVectors( GetAbsAngles(), &forward, NULL, &up );
pHurt->ApplyAbsVelocityImpulse( 100 * (up-forward) );*/
Vector forward, up;
AngleVectors(GetLocalAngles(), &forward, NULL, &up);
pHurt->ApplyAbsVelocityImpulse(forward * 100 + up * 100);
pHurt->SetGroundEntity( NULL );
}
}
break;
/*case BSQUID_AE_WHIP_SND:
{
EmitSound( "NPC_Bullsquid.TailWhip" );
break;
}*/
case BSQUID_AE_TAILWHIP:// Light Kill : Seriously Valve why you remove this ?
{
EmitSound( "NPC_Bullsquid.TailWhip" );
CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_whip.GetFloat(), DMG_SLASH | DMG_ALWAYSGIB );
if ( pHurt )
{
//Vector right, up;
//AngleVectors( GetAbsAngles(), NULL, &right, &up );
Vector forward, up;
AngleVectors(GetLocalAngles(), &forward, NULL, &up);
if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
pHurt->ViewPunch( QAngle( 20, 0, -20 ) );
pHurt->ApplyAbsVelocityImpulse(forward * 100 + up * 100);
//pHurt->ApplyAbsVelocityImpulse( 100 * (up+2*right) );
}
}
break;
case BSQUID_AE_BLINK:
{
// close eye.
m_nSkin = 1;
}
break;
case BSQUID_AE_HOP:
{
float flGravity = sv_gravity.GetFloat();
// throw the squid up into the air on this frame.
if ( GetFlags() & FL_ONGROUND )
{
SetGroundEntity( NULL );
}
// jump 40 inches into the air
Vector vecVel = GetAbsVelocity();
vecVel.z += sqrt( flGravity * 2.0 * 40 );
SetAbsVelocity( vecVel );
}
break;
case BSQUID_AE_THROW:
{
// squid throws its prey IF the prey is a client.
CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), 0, 0 );
if ( pHurt )
{
pHurt->ViewPunch( QAngle(20,0,-20) );
// screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels.
UTIL_ScreenShake( pHurt->GetAbsOrigin(), 25.0, 1.5, 0.7, 2, SHAKE_START );
// If the player, throw him around
if ( pHurt->IsPlayer())
{
Vector forward, up;
AngleVectors( GetLocalAngles(), &forward, NULL, &up );
pHurt->ApplyAbsVelocityImpulse( forward * 150 + up * 150 );
}
// If not the player see if has bullsquid throw interatcion
else
{
CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pHurt );
if (pVictim)
{
if ( pVictim->DispatchInteraction( g_interactionBullsquidThrow, NULL, this ) )
{
Vector forward, up;
AngleVectors( GetLocalAngles(), &forward, NULL, &up );
pVictim->ApplyAbsVelocityImpulse( forward * 150 + up * 100 );
}
}
}
}
}
break;
default:
BaseClass::HandleAnimEvent( pEvent );
}
}
Vector VecCheckThrowToleranceUpd( CBaseEntity *pEdict, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flTolerance )
{
flSpeed = max( 1.0f, flSpeed );
float flGravity = sv_gravity.GetFloat();
Vector vecGrenadeVel = (vecSpot2 - vecSpot1);
// throw at a constant time
float time = vecGrenadeVel.Length( ) / flSpeed;
vecGrenadeVel = vecGrenadeVel * (1.0 / time);
// adjust upward toss to compensate for gravity loss
vecGrenadeVel.z += flGravity * time * 0.5;
Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5;
vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5);
trace_t tr;
UTIL_TraceLine( vecSpot1, vecApex, MASK_SOLID, pEdict, COLLISION_GROUP_NONE, &tr );
if (tr.fraction != 1.0)
{
return vec3_origin;
}
UTIL_TraceLine( vecApex, vecSpot2, MASK_SOLID_BRUSHONLY, pEdict, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction != 1.0 )
{
bool bFail = true;
// Didn't make it all the way there, but check if we're within our tolerance range
if ( flTolerance > 0.0f )
{
float flNearness = ( tr.endpos - vecSpot2 ).LengthSqr();
if ( flNearness < Square( flTolerance ) )
{
bFail = false;
}
}
if ( bFail )
{
return vec3_origin;
}
}
return vecGrenadeVel;
}
bool CNPC_Bullsquid::GetSpitVector( const Vector &vecStartPos, const Vector &vecTarget, Vector *vecOut )
{
// Try the most direct route
Vector vecToss = VecCheckThrowToleranceUpd( this, vecStartPos, vecTarget, /*sk_bullsquid_spit_speed.GetFloat()*/ 600, (10.0f*12.0f) );
// If this failed then try a little faster (flattens the arc)
if ( vecToss == vec3_origin )
{
vecToss = VecCheckThrowToleranceUpd( this, vecStartPos, vecTarget, /*sk_bullsquid_spit_speed.GetFloat()*/ 600 * 1.5f, (10.0f*12.0f) );
if ( vecToss == vec3_origin )
return false;
}
// Save out the result
if ( vecOut )
{
*vecOut = vecToss;
}
return true;
}
int CNPC_Bullsquid::RangeAttack1Conditions( float flDot, float flDist )
{
if ( IsMoving() && flDist >= 512 )
{
// squid will far too far behind if he stops running to spit at this distance from the enemy.
return ( COND_NONE );
}
if ( flDist > 85 && flDist <= 784 && flDot >= 0.5 && gpGlobals->curtime >= m_flNextSpitTime )
{
if ( GetEnemy() != NULL )
{
if ( fabs( GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z ) > 256 )
{
// don't try to spit at someone up really high or down really low.
return( COND_NONE );
}
}
if ( IsMoving() )
{
// don't spit again for a long time, resume chasing enemy.
m_flNextSpitTime = gpGlobals->curtime + 5;
}
else
{
// not moving, so spit again pretty soon.
m_flNextSpitTime = gpGlobals->curtime + 0.5;
}
return( COND_CAN_RANGE_ATTACK1 );
}
return( COND_NONE );
}
//=========================================================
// MeleeAttack2Conditions - bullsquid is a big guy, so has a longer
// melee range than most monsters. This is the tailwhip attack
//=========================================================
int CNPC_Bullsquid::MeleeAttack1Conditions( float flDot, float flDist )
{
if ( GetEnemy()->m_iHealth <= sk_bullsquid_dmg_whip.GetFloat() && flDist <= 85 && flDot >= 0.7 )
{
return ( COND_CAN_MELEE_ATTACK1 ); //1
}
return( COND_NONE );
}
//=========================================================
// MeleeAttack2Conditions - bullsquid is a big guy, so has a longer
// melee range than most monsters. This is the bite attack.
// this attack will not be performed if the tailwhip attack
// is valid.
//=========================================================
int CNPC_Bullsquid::MeleeAttack2Conditions( float flDot, float flDist )
{
if ( flDist <= 85 && flDot >= 0.7 && !HasCondition( COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes
return ( COND_CAN_MELEE_ATTACK2 );
return( COND_NONE );
}
bool CNPC_Bullsquid::FValidateHintType( CAI_Hint *pHint )
{
if ( pHint->HintType() == HINT_HL1_WORLD_HUMAN_BLOOD )
return true;
DevMsg( "Couldn't validate hint type" );
return false;
}
void CNPC_Bullsquid::RemoveIgnoredConditions( void )
{
if ( m_flHungryTime > gpGlobals->curtime )
ClearCondition( COND_SQUID_SMELL_FOOD );
if ( gpGlobals->curtime - m_flLastHurtTime <= 20 )
{
// haven't been hurt in 20 seconds, so let the squid care about stink.
ClearCondition( COND_SMELL );
}
if ( GetEnemy() != NULL )
{
// ( Unless after a tasty headcrab, yumm ^_^ )
if ( FClassnameIs( GetEnemy(), "npc_headcrab" ) )
ClearCondition( COND_SMELL );
}
}
Disposition_t CNPC_Bullsquid::IRelationType( CBaseEntity *pTarget )
{
if ( gpGlobals->curtime - m_flLastHurtTime < 5 && FClassnameIs( pTarget, "npc_headcrab" ) )
{
// if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab,
// tell squid to disregard crab.
return D_NU;
}
return BaseClass::IRelationType( pTarget );
}
//=========================================================
// TakeDamage - overridden for bullsquid so we can keep track
// of how much time has passed since it was last injured
//=========================================================
int CNPC_Bullsquid::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
#if 0 //Fix later.
float flDist;
Vector vecApex, vOffset;
// if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy,
// it will swerve. (whew).
if ( GetEnemy() != NULL && IsMoving() && pevAttacker == GetEnemy() && gpGlobals->curtime - m_flLastHurtTime > 3 )
{
flDist = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).Length2D();
if ( flDist > SQUID_SPRINT_DIST )
{
AI_Waypoint_t* pRoute = GetNavigator()->GetPath()->Route();
if ( pRoute )
{
flDist = ( GetAbsOrigin() - pRoute[ pRoute->iNodeID ].vecLocation ).Length2D();// reusing flDist.
if ( GetNavigator()->GetPath()->BuildTriangulationRoute( GetAbsOrigin(), pRoute[ pRoute->iNodeID ].vecLocation, flDist * 0.5, GetEnemy(), &vecApex, &vOffset, NAV_GROUND ) )
{
GetNavigator()->PrependWaypoint( vecApex, bits_WP_TO_DETOUR | bits_WP_DONT_SIMPLIFY );
}
}
}
}
#endif
if (inputInfo.GetAttacker() && !FClassnameIs(inputInfo.GetAttacker(), "npc_headcrab"))
{
// don't forget about headcrabs if it was a headcrab that hurt the squid.
m_flLastHurtTime = gpGlobals->curtime;
}
/*if (cvar->FindVar("oc_ragdoll_enable_blooddrips")->GetInt())
{
int MaxDripsGreen = RandomInt(4, 7);
Vector vecToss, vecTraceDir;
if (inputInfo.GetDamageType() & (DMG_BULLET | DMG_SHOCK))
{
for (int i = 0; i < MaxDripsGreen; i++)
{
vecTraceDir = WorldSpaceCenter();
if (LastHitGroup() == HITGROUP_CHEST)
{
vecTraceDir.x += random->RandomFloat(-0.1, 0.1);
vecTraceDir.y += random->RandomFloat(-0.1, 0.1);
vecTraceDir.z += random->RandomFloat(-0.1, 0.1);
}
if (LastHitGroup() == HITGROUP_HEAD)
{
vecTraceDir.x += random->RandomFloat(0.1, 0.4);
vecTraceDir.y += random->RandomFloat(0.1, 0.4);
vecTraceDir.z += random->RandomFloat(12.1, 12.4);
}
if (LastHitGroup() == HITGROUP_LEFTARM)
{
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
vecTraceDir.y += random->RandomFloat(-12.1, -12.4);
vecTraceDir.z += random->RandomFloat(0.1, 0.2);
}
if (LastHitGroup() == HITGROUP_RIGHTARM)
{
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
vecTraceDir.y += random->RandomFloat(12.1, 12.4);
vecTraceDir.z += random->RandomFloat(0.1, 0.2);
}
if (LastHitGroup() == HITGROUP_LEFTLEG)
{
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
vecTraceDir.y += random->RandomFloat(-12.1, -12.4);
vecTraceDir.z += random->RandomFloat(-20.1, -20.2);
}
if (LastHitGroup() == HITGROUP_RIGHTLEG)
{
vecTraceDir.x += random->RandomFloat(0.1, 0.2);
vecTraceDir.y += random->RandomFloat(12.1, 12.4);
vecTraceDir.z += random->RandomFloat(-20.1, -20.2);
}
// Set the velocity
Vector vecVelocity;
AngularImpulse angImpulse;
QAngle angles;
angles.x = random->RandomFloat(-70, 20);
angles.y = random->RandomFloat(0, 360);
angles.z = 0.0f;
AngleVectors(angles, &vecVelocity);
vecVelocity *= random->RandomFloat(150, 300);
vecVelocity += GetAbsVelocity();
angImpulse = RandomAngularImpulse(-180, 180);
CBloodDripsGreen *pGrenade = (CBloodDripsGreen*)CreateEntityByName("BloodDripsGreen");
//pGrenade->SetAbsOrigin(vecTraceDir);
pGrenade->SetLocalOrigin(vecTraceDir);
pGrenade->SetAbsAngles(RandomAngle(0, 360));
DispatchSpawn(pGrenade);
pGrenade->SetThrower(this);
pGrenade->SetOwnerEntity(this);
pGrenade->SetAbsVelocity(vecVelocity);
}
}
}*/
return BaseClass::OnTakeDamage_Alive( inputInfo );
}
//=========================================================
// GetSoundInterests - returns a bit mask indicating which types
// of sounds this monster regards. In the base class implementation,
// monsters care about all sounds, but no scents.
//=========================================================
int CNPC_Bullsquid::GetSoundInterests( void )
{
return SOUND_WORLD |
SOUND_COMBAT |
SOUND_CARCASS |
SOUND_MEAT |
SOUND_GARBAGE |
SOUND_PLAYER;
}
//=========================================================
// OnListened - monsters dig through the active sound list for
// any sounds that may interest them. (smells, too!)
//=========================================================
void CNPC_Bullsquid::OnListened( void )
{
AISoundIter_t iter;
CSound *pCurrentSound;
static int conditionsToClear[] =
{
COND_SQUID_SMELL_FOOD,
};
ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
while ( pCurrentSound )
{
// the npc cares about this sound, and it's close enough to hear.
int condition = COND_NONE;
if ( !pCurrentSound->FIsSound() )
{
// if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent
if ( pCurrentSound->m_iType & ( SOUND_MEAT | SOUND_CARCASS ) )
{
// the detected scent is a food item
condition = COND_SQUID_SMELL_FOOD;
}
}
if ( condition != COND_NONE )
SetCondition( condition );
pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
}
BaseClass::OnListened();
}
//========================================================
// RunAI - overridden for bullsquid because there are things
// that need to be checked every think.
//========================================================
void CNPC_Bullsquid::RunAI( void )
{
// first, do base class stuff
BaseClass::RunAI();
if ( m_nSkin != 0 )
{
// close eye if it was open.
m_nSkin = 0;
}
if ( random->RandomInt( 0,39 ) == 0 )
{
m_nSkin = 1;
}
if ( GetEnemy() != NULL && GetActivity() == ACT_RUN )
{
// chasing enemy. Sprint for last bit
if ( (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length2D() < SQUID_SPRINT_DIST )
{
m_flPlaybackRate = 1.25;
}
}
}
//=========================================================
// GetSchedule
//=========================================================
int CNPC_Bullsquid::SelectSchedule( void )
{
switch ( m_NPCState )
{
case NPC_STATE_ALERT:
{
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
{
return SCHED_SQUID_HURTHOP;
}
if ( HasCondition( COND_SQUID_SMELL_FOOD ) )
{
CSound *pSound;
pSound = GetBestScent();
if ( pSound && (!FInViewCone( pSound->GetSoundOrigin() ) || !FVisible( pSound->GetSoundOrigin() )) )
{
// scent is behind or occluded
return SCHED_SQUID_SNIFF_AND_EAT;
}
// food is right out in the open. Just go get it.
return SCHED_SQUID_EAT;
}
if ( HasCondition( COND_SMELL ) )
{
// there's something stinky.
CSound *pSound;
pSound = GetBestScent();
if ( pSound )
return SCHED_SQUID_WALLOW;
}
break;
}
case NPC_STATE_COMBAT:
{
// dead enemy
if ( HasCondition( COND_ENEMY_DEAD ) )
{
// call base class, all code to handle dead enemies is centralized there.
return BaseClass::SelectSchedule();
}
if ( HasCondition( COND_NEW_ENEMY ) )
{
if ( m_fCanThreatDisplay && IRelationType( GetEnemy() ) == D_HT && FClassnameIs( GetEnemy(), "npc_headcrab" ) )
{
// this means squid sees a headcrab!
m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime.
return SCHED_SQUID_SEECRAB;
}
else
{
return SCHED_WAKE_ANGRY;
}
}
if ( HasCondition( COND_SQUID_SMELL_FOOD ) )
{
CSound *pSound;
pSound = GetBestScent();
if ( pSound && (!FInViewCone( pSound->GetSoundOrigin() ) || !FVisible( pSound->GetSoundOrigin() )) )
{
// scent is behind or occluded
return SCHED_SQUID_SNIFF_AND_EAT;
}
// food is right out in the open. Just go get it.
return SCHED_SQUID_EAT;
}
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
{
return SCHED_RANGE_ATTACK1;
}
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
{
return SCHED_MELEE_ATTACK1;
}
if ( HasCondition( COND_CAN_MELEE_ATTACK2 ) )
{
return SCHED_MELEE_ATTACK2;
}
return SCHED_CHASE_ENEMY;
break;
}
}
return BaseClass::SelectSchedule();
}
//=========================================================
// FInViewCone - returns true is the passed vector is in
// the caller's forward view cone. The dot product is performed
// in 2d, making the view cone infinitely tall.
//=========================================================
bool CNPC_Bullsquid::FInViewCone( Vector pOrigin )
{
Vector los = ( pOrigin - GetAbsOrigin() );
// do this in 2D
los.z = 0;
VectorNormalize( los );
Vector facingDir = EyeDirection2D( );
float flDot = DotProduct( los, facingDir );
if ( flDot > m_flFieldOfView )
return true;
return false;
}
//=========================================================
// Start task - selects the correct activity and performs
// any necessary calculations to start the next task on the
// schedule. OVERRIDDEN for bullsquid because it needs to
// know explicitly when the last attempt to chase the enemy
// failed, since that impacts its attack choices.
//=========================================================
void CNPC_Bullsquid::StartTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_MELEE_ATTACK2:
{
if (GetEnemy())
{
GrowlSound();
m_flLastAttackTime = gpGlobals->curtime;
BaseClass::StartTask( pTask );
}
break;
}
case TASK_SQUID_HOPTURN:
{
SetActivity( ACT_HOP );
if ( GetEnemy() )
{
Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
VectorNormalize( vecFacing );
GetMotor()->SetIdealYaw( vecFacing );
}
break;
}
case TASK_SQUID_EAT:
{
m_flHungryTime = gpGlobals->curtime + pTask->flTaskData;
TaskComplete();
break;
}
default:
{
BaseClass::StartTask( pTask );
break;
}
}
}
//=========================================================
// RunTask
//=========================================================
void CNPC_Bullsquid::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_SQUID_HOPTURN:
{
if ( GetEnemy() )
{
Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
VectorNormalize( vecFacing );
GetMotor()->SetIdealYaw( vecFacing );
}
if ( IsSequenceFinished() )
{
TaskComplete();
}
break;
}
default:
{
BaseClass::RunTask( pTask );
break;
}
}
}
//=========================================================
// GetIdealState - Overridden for Bullsquid to deal with
// the feature that makes it lose interest in headcrabs for
// a while if something injures it.
//=========================================================
NPC_STATE CNPC_Bullsquid::SelectIdealState( void )
{
// If no schedule conditions, the new ideal state is probably the reason we're in here.
switch ( m_NPCState )
{
case NPC_STATE_COMBAT:
{
// COMBAT goes to ALERT upon death of enemy
if ( GetEnemy() != NULL && ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) && FClassnameIs( GetEnemy(), "npc_headcrab" ) )
{
// if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while.
SetEnemy( NULL );
return NPC_STATE_ALERT;
}
break;
}
}
return BaseClass::SelectIdealState();
}
//------------------------------------------------------------------------------
//
// Schedules
//
//------------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC( npc_bullsquid, CNPC_Bullsquid )
DECLARE_TASK( TASK_SQUID_HOPTURN )
DECLARE_TASK( TASK_SQUID_EAT )
DECLARE_CONDITION( COND_SQUID_SMELL_FOOD )
DECLARE_ACTIVITY( ACT_SQUID_EXCITED )
DECLARE_ACTIVITY( ACT_SQUID_EAT )
DECLARE_ACTIVITY( ACT_SQUID_DETECT_SCENT )
DECLARE_ACTIVITY( ACT_SQUID_INSPECT_FLOOR )
DECLARE_INTERACTION( g_interactionBullsquidThrow )
//=========================================================
// > SCHED_SQUID_HURTHOP
//=========================================================
DEFINE_SCHEDULE
(
SCHED_SQUID_HURTHOP,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SOUND_WAKE 0"
" TASK_SQUID_HOPTURN 0"
" TASK_FACE_ENEMY 0"
" "
" Interrupts"
)
//=========================================================
// > SCHED_SQUID_SEECRAB
//=========================================================
DEFINE_SCHEDULE
(
SCHED_SQUID_SEECRAB,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SOUND_WAKE 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EXCITED"
" TASK_FACE_ENEMY 0"
" "
" Interrupts"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
)
//=========================================================
// > SCHED_SQUID_EAT
//=========================================================
DEFINE_SCHEDULE
(
SCHED_SQUID_EAT,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SQUID_EAT 10"
" TASK_STORE_LASTPOSITION 0"
" TASK_GET_PATH_TO_BESTSCENT 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
" TASK_SQUID_EAT 50"
" TASK_GET_PATH_TO_LASTPOSITION 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_CLEAR_LASTPOSITION 0"
" "
" Interrupts"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_NEW_ENEMY"
" COND_SMELL"
)
//=========================================================
// > SCHED_SQUID_SNIFF_AND_EAT
//=========================================================
DEFINE_SCHEDULE
(
SCHED_SQUID_SNIFF_AND_EAT,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SQUID_EAT 10"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_DETECT_SCENT"
" TASK_STORE_LASTPOSITION 0"
" TASK_GET_PATH_TO_BESTSCENT 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
" TASK_SQUID_EAT 50"
" TASK_GET_PATH_TO_LASTPOSITION 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_CLEAR_LASTPOSITION 0"
" "
" Interrupts"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_NEW_ENEMY"
" COND_SMELL"
)
//=========================================================
// > SCHED_SQUID_WALLOW
//=========================================================
DEFINE_SCHEDULE
(
SCHED_SQUID_WALLOW,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SQUID_EAT 10"
" TASK_STORE_LASTPOSITION 0"
" TASK_GET_PATH_TO_BESTSCENT 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_INSPECT_FLOOR"
" TASK_SQUID_EAT 50"
" TASK_GET_PATH_TO_LASTPOSITION 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_CLEAR_LASTPOSITION 0"
" "
" Interrupts"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_NEW_ENEMY"
)
AI_END_CUSTOM_NPC()