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

1442 lines
38 KiB
C++

//========= Copyright © 2019, Overcharged. ============//
//
// Purpose: Gonome. Opposing Force big monster recreation.
//
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "game.h"
#include "AI_Default.h"
#include "AI_Schedule.h"
#include "AI_Hull.h"
#include "AI_Route.h"
#include "AI_Hint.h"
#include "AI_Navigator.h"
#include "AI_Senses.h"
#include "NPCEvent.h"
#include "animation.h"
#include "overcharged/npc_gonome.h"
#include "gib.h"
#include "soundent.h"
#include "ndebugoverlay.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "util.h"
#include "shake.h"
#include "movevars_shared.h"
#include "decals.h"
#include "hl2_shareddefs.h"
#include "hl2_gamerules.h"
#include "ammodef.h"
#include "player.h"
#include "ai_network.h"
#include "ai_navigator.h"
#include "ai_motor.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_node.h"
#include "ai_memory.h"
#include "ai_senses.h"
#include "bitstring.h"
#include "EntityFlame.h"
#include "hl2_shareddefs.h"
#include "npcevent.h"
#include "activitylist.h"
#include "entitylist.h"
#include "gib.h"
#include "soundenvelope.h"
#include "ndebugoverlay.h"
#include "rope.h"
#include "rope_shared.h"
#include "igamesystem.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "props.h"
#include "hl2_gamerules.h"
#include "weapon_physcannon.h"
#include "ammodef.h"
#include "vehicle_base.h"
#include "particle_parse.h" // particles
#include "particle_system.h"
#define GONOME_SPRINT_DIST 256
#define ZOMBIE_BURN_TIME 10 // If ignited, burn for this many seconds
#define ZOMBIE_BURN_TIME_NOISE 2 // Give or take this many seconds.
ConVar sk_gonome_health ( "sk_gonome_health", "300" );
ConVar sk_gonome_dmg_bite("sk_gonome_dmg_bite", "25");
ConVar sk_gonome_dmg_whip("sk_gonome_dmg_whip", "35");
ConVar sk_gonome_dmg_spit("sk_gonome_dmg_spit", "15");
//=========================================================
// monster-specific schedule types
//=========================================================
enum
{
SCHED_GONOME_HURTHOP = LAST_SHARED_SCHEDULE,
SCHED_GONOME_SEECRAB,
SCHED_GONOME_EAT,
SCHED_GONOME_SNIFF_AND_EAT,
SCHED_GONOME_WALLOW,
SCHED_GONOME_CHASE_ENEMY,
};
//=========================================================
// monster-specific tasks
//=========================================================
enum
{
TASK_GONOME_HOPTURN = LAST_SHARED_TASK,
TASK_GONOME_EAT,
};
//-----------------------------------------------------------------------------
// Gonome Conditions
//-----------------------------------------------------------------------------
enum
{
COND_GONOME_SMELL_FOOD = LAST_SHARED_CONDITION + 1,
};
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define GONOME_AE_SPIT ( 1 )
#define GONOME_AE_BITE ( 2 )
#define GONOME_AE_BLINK ( 3 )
#define GONOME_AE_TAILWHIP ( 4 )
#define GONOME_AE_HOP ( 5 )
#define GONOME_AE_THROW ( 6 )
#define GONOME_AE_FOOTSTEP ( 7 )
LINK_ENTITY_TO_CLASS( npc_gonome, CNPC_Gonome ); // BriJee: Gonome ent definition
int ACT_GONOME_EXCITED;
int ACT_GONOME_EAT;
int ACT_GONOME_DETECT_SCENT;
int ACT_GONOME_INSPECT_FLOOR;
//=========================================================
// Gonome's spit
//=========================================================
class CGonomeSpit : public CBaseEntity
{
DECLARE_CLASS( CGonomeSpit, CBaseEntity );
public:
void Spawn( void );
void Precache( void );
static void Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity );
void Touch( CBaseEntity *pOther );
void Animate( void );
int m_nGonomeSpitSprite;
DECLARE_DATADESC();
void SetSprite( CBaseEntity *pSprite )
{
m_hSprite = pSprite;
}
CBaseEntity *GetSprite( void )
{
return m_hSprite.Get();
}
private:
EHANDLE m_hSprite;
};
LINK_ENTITY_TO_CLASS( gonome_spit, CGonomeSpit );
BEGIN_DATADESC( CGonomeSpit )
DEFINE_FIELD( m_nGonomeSpitSprite, FIELD_INTEGER ),
DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ),
END_DATADESC()
void CGonomeSpit::Precache( void )
{
//m_nGonomeSpitSprite = PrecacheModel("sprites/gonomespit.vmt");// client side spittle.
//PrecacheModel("models/gibs/hgibs_scapula.mdl"); // BriJee: Projectile Gib model
//PrecacheModel("models/weapons/hunter_flechette.mdl");
PrecacheScriptSound( "Npc_GonomeSpit.SpitTouch1" );
PrecacheScriptSound( "Npc_GonomeSpit.SpitHit1" );
PrecacheScriptSound( "Npc_GonomeSpit.SpitHit2" );
PrecacheScriptSound( "Zombie.AttackHit" );
PrecacheScriptSound( "Zombie.AttackMiss" );
PrecacheParticleSystem("blood_impact_red_01_chunk"); //BriJee: Main projectile particle
PrecacheParticleSystem("blood_spit_trail");
}
void CGonomeSpit:: Spawn( void )
{
Precache();
SetMoveType ( MOVETYPE_FLY );
SetClassname( "gonome_spit" );
SetSolid( SOLID_BBOX );
//m_nRenderMode = kRenderTransAlpha;
//SetRenderColorA( 255 );
//SetModel( "models/gibs/hgibs_scapula.mdl" ); // BriJee: Projectile Gib model
//SetModel("models/weapons/hunter_flechette.mdl");
SetModel("");
SetSprite( CSprite::SpriteCreate( /*"sprites/gonomespit.vmt"*/ NULL, GetAbsOrigin(), true ) ); // BriJee: NULL sprite name, we will attach particle on it
//SetRenderColor( 150, 0, 0, 255 );
// Create the dust effect in place
CParticleSystem *pParticle = (CParticleSystem *)CreateEntityByName("info_particle_system");
if (pParticle != NULL)
{
pParticle->KeyValue("start_active", "1");
//pParticle->KeyValue("effect_name", "blood_impact_red_01_chunk");
pParticle->KeyValue("effect_name", "blood_spit_trail"); // BJ: wtf? blood_trail is unknown, found blood_spit_trail in particles
pParticle->SetParent(this);
//pParticle->SetParentAttachment("SetParentAttachment", "attach_glow2", true);
//pParticle->SetLocalOrigin(Vector(-16, 24, 0));
pParticle->SetLocalOrigin(vec3_origin);
DispatchSpawn(pParticle);
if (gpGlobals->curtime > 0.5f)
pParticle->Activate();
}
UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
SetCollisionGroup( HL2COLLISION_GROUP_SPIT );
}
void CGonomeSpit::Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity )
{
CGonomeSpit *pSpit = CREATE_ENTITY( CGonomeSpit, "gonome_spit" );
pSpit->Spawn();
UTIL_SetOrigin( pSpit, vecStart );
pSpit->SetAbsVelocity( vecVelocity );
pSpit->SetOwnerEntity( pOwner );
CSprite *pSprite = (CSprite*)pSpit->GetSprite();
if ( pSprite )
{
pSprite->SetAttachment( pSpit, 0 );
pSprite->SetOwnerEntity( pSpit );
pSprite->SetScale( 0.75 );
//pSprite->SetTransparency( pSpit->m_nRenderMode, pSpit->m_clrRender->r, pSpit->m_clrRender->g, pSpit->m_clrRender->b, pSpit->m_clrRender->a, pSpit->m_nRenderFX );
}
CPVSFilter filter( vecStart );
VectorNormalize( vecVelocity );
te->SpriteSpray( filter, 0.0, &vecStart , &vecVelocity, pSpit->m_nGonomeSpitSprite, 210, 25, 15 );
}
void CGonomeSpit::Touch ( CBaseEntity *pOther )
{
trace_t tr;
int iPitch;
if ( pOther->GetSolidFlags() & FSOLID_TRIGGER )
return;
if ( pOther->GetCollisionGroup() == HL2COLLISION_GROUP_SPIT)
{
return;
}
// splat sound
iPitch = random->RandomFloat( 90, 110 );
EmitSound( "Npc_GonomeSpit.SpitTouch1" );
switch ( random->RandomInt( 0, 1 ) )
{
case 0:
EmitSound( "Npc_GonomeSpit.SpitHit1" );
break;
case 1:
EmitSound( "Npc_GonomeSpit.SpitHit2" );
break;
}
if ( !pOther->m_takedamage )
{
// make a splat on the wall
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 10, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
UTIL_DecalTrace(&tr, "Blood" ); //BeerSplash
// make some flecks
CPVSFilter filter( tr.endpos );
te->SpriteSpray( filter, 0.0, &tr.endpos, &tr.plane.normal, m_nGonomeSpitSprite, 30, 8, 5 );
}
else
{
CTakeDamageInfo info(this, this, sk_gonome_dmg_spit.GetFloat(), DMG_BULLET);
CalculateBulletDamageForce( &info, GetAmmoDef()->Index("Pistol"), GetAbsVelocity(), GetAbsOrigin() ); // BriJee: Ammotype fixup for HL2
pOther->TakeDamage( info );
}
UTIL_Remove( m_hSprite );
UTIL_Remove( this );
}
BEGIN_DATADESC( CNPC_Gonome )
DEFINE_FIELD( m_fCanThreatDisplay, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flLastHurtTime, FIELD_TIME ),
DEFINE_FIELD( m_flNextSpitTime, FIELD_TIME ),
DEFINE_FIELD( m_flBurnDamage, FIELD_FLOAT ),
DEFINE_FIELD( m_flBurnDamageResetTime, FIELD_TIME ),
DEFINE_FIELD( m_flHungryTime, FIELD_TIME ),
END_DATADESC()
//=========================================================
// Spawn
//=========================================================
void CNPC_Gonome::Spawn()
{
Precache( );
SetModel("models/zombie/gonome.mdl");
SetBodygroup(ZOMBIE_BODYGROUP_HEADCRAB, 1); // BriJee: Bodygroup on spawn.
SetHullType(HULL_MEDIUM_TALL);//HULL_HUMAN);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
m_bloodColor = BLOOD_COLOR_YELLOW;
SetRenderColor( 255, 255, 255, 255 );
m_iHealth = sk_gonome_health.GetFloat();
m_flFieldOfView = 0.2;
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_MOVE_JUMP | bits_CAP_MOVE_CLIMB );
CapabilitiesAdd( bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_DOORS_GROUP );
m_fCanThreatDisplay = TRUE;
m_flNextSpitTime = gpGlobals->curtime;
NPCInit();
m_flDistTooFar = 784;
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Gonome::Precache()
{
BaseClass::Precache();
PrecacheModel("models/zombie/gonome.mdl");
//PrecacheModel("sprites/gonomespit.vmt");
PrecacheScriptSound( "Npc_Gonome.Idle" );
PrecacheScriptSound( "Npc_Gonome.Pain" );
PrecacheScriptSound( "Npc_Gonome.Alert" );
PrecacheScriptSound( "Npc_Gonome.Die" );
PrecacheScriptSound( "Npc_Gonome.Attack" );
PrecacheScriptSound( "Npc_Gonome.Bite" );
PrecacheScriptSound( "Npc_Gonome.Growl" );
PrecacheScriptSound("Npc_Gonome.Step1"); // new
PrecacheScriptSound("Npc_Gonome.Step2");
}
int CNPC_Gonome::TranslateSchedule( int scheduleType )
{
switch ( scheduleType )
{
case SCHED_CHASE_ENEMY:
return SCHED_GONOME_CHASE_ENEMY;
break;
}
return BaseClass::TranslateSchedule( scheduleType );
}
// BJ: Gonome HEADSHOT damage code
void CNPC_Gonome::TraceAttack(const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator)
{
CTakeDamageInfo ainfo = info;
float flDamage = ainfo.GetDamage();
// BJ Hitgroup 1 - head
if (ptr->hitgroup == 1 && (ainfo.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB))) // only bullet, melee damage filter
{
flDamage += 50; // BJ: x2 damage in head
if (flDamage <= 0)
flDamage = 0.1;
ainfo.SetDamage(flDamage);
}
SpawnBlood(ptr->endpos, vecDir, BloodColor(), flDamage);
TraceBleed(flDamage, vecDir, ptr, ainfo.GetDamageType());
DevMsg("Gonome damagevalue %.2f \n", flDamage);
AddMultiDamage(ainfo, this);
}
//-----------------------------------------------------------------------------
// Purpose: Indicates this monster's place in the relationship table.
// Output :
//-----------------------------------------------------------------------------
Class_T CNPC_Gonome::Classify( void )
{
return CLASS_ZOMBIE;
}
bool CNPC_Gonome::IsJumpLegal(const Vector &startPos, const Vector &apex, const Vector &endPos) const
{
const float MAX_JUMP_RISE = 400.0f;
const float MAX_JUMP_DISTANCE = 800.0f;
const float MAX_JUMP_DROP = 2048.0f;
if ( BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ) )
{
// Hang onto the jump distance. The AI is going to want it.
m_flJumpDist = (startPos - endPos).Length();
return true;
}
return false;
}
//=========================================================
// IdleSound
//=========================================================
#define GONOME_ATTN_IDLE (float)1.5
void CNPC_Gonome::IdleSound( void )
{
CPASAttenuationFilter filter( this, GONOME_ATTN_IDLE );
EmitSound( filter, entindex(), "Npc_Gonome.Idle" );
}
//=========================================================
// PainSound
//=========================================================
void CNPC_Gonome::PainSound( const CTakeDamageInfo &info )
{
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Npc_Gonome.Pain" );
}
//=========================================================
// AlertSound
//=========================================================
void CNPC_Gonome::AlertSound( void )
{
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Npc_Gonome.Alert" );
}
//=========================================================
// DeathSound
//=========================================================
void CNPC_Gonome::DeathSound( const CTakeDamageInfo &info )
{
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Npc_Gonome.Die" );
}
//=========================================================
// AttackSound
//=========================================================
void CNPC_Gonome::AttackSound( void )
{
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Npc_Gonome.Attack" );
}
//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
float CNPC_Gonome::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;
}
bool CNPC_Gonome::ShouldIgnite( const CTakeDamageInfo &info )
{
if ( IsOnFire() )
{
// Already burning!
return false;
}
if ( info.GetDamageType() & DMG_BURN )
{
//
// If we take more than ten percent of our health in burn damage within a five
// second interval, we should catch on fire.
//
m_flBurnDamage += info.GetDamage();
m_flBurnDamageResetTime = gpGlobals->curtime + 5;
if ( m_flBurnDamage >= m_iMaxHealth * 0.1 )
{
Ignite(100.0f);
return true;
}
}
return false;
}
void CNPC_Gonome::PrescheduleThink( void )
{
BaseClass::PrescheduleThink();
if ( ( m_flBurnDamageResetTime ) && ( gpGlobals->curtime >= m_flBurnDamageResetTime ) )
{
m_flBurnDamage = 0;
}
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Gonome::HandleAnimEvent( animevent_t *pEvent )
{
switch( pEvent->event )
{
case GONOME_AE_FOOTSTEP:
{
CPASAttenuationFilter filter(this);
int i = random->RandomInt(0, 1);
if (i)
EmitSound(filter, entindex(), "Npc_Gonome.Step1");
else
EmitSound(filter, entindex(), "Npc_Gonome.Step2");
}
break;
case GONOME_AE_SPIT:
{
if ( GetEnemy() )
{
Vector vecSpitOffset;
Vector vecSpitDir;
Vector vRight, vUp, vForward;
AngleVectors ( GetAbsAngles(), &vForward, &vRight, &vUp );
// !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here.
// we should be able to read the position of bones at runtime for this info.
vecSpitOffset = ( vRight * 8 + vForward * 60 + vUp * 50 );
vecSpitOffset = ( GetAbsOrigin() + vecSpitOffset );
vecSpitDir = ( ( GetEnemy()->BodyTarget( GetAbsOrigin() ) ) - vecSpitOffset );
VectorNormalize( vecSpitDir );
vecSpitDir.x += random->RandomFloat( -0.05, 0.05 );
vecSpitDir.y += random->RandomFloat( -0.05, 0.05 );
vecSpitDir.z += random->RandomFloat( -0.05, 0 );
AttackSound();
CGonomeSpit::Shoot( this, vecSpitOffset, vecSpitDir * 900 );
}
}
break;
case GONOME_AE_BITE:
{
// SOUND HERE!
CPASAttenuationFilter filter( this );
CBaseEntity *pHurt = CheckTraceHullAttack(70, Vector(-16, -16, -16), Vector(16, 16, 16), sk_gonome_dmg_bite.GetFloat(), DMG_SLASH);
if ( pHurt )
{
Vector forward, up;
AngleVectors( GetAbsAngles(), &forward, NULL, &up );
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (forward * 100) );
//pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (up * 100) );
if (pHurt->GetFlags() & (FL_NPC | FL_CLIENT))
pHurt->ViewPunch(QAngle(random->RandomInt(-20, 20), random->RandomInt(-20, 20), random->RandomInt(-20, 20)));
pHurt->SetGroundEntity( NULL );
EmitSound( filter, entindex(), "Zombie.AttackHit" );
}
else // Play a random attack miss sound
EmitSound( filter, entindex(), "Zombie.AttackMiss" );
}
break;
case GONOME_AE_TAILWHIP:
{
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Npc_Gonome.Bite" );
CBaseEntity *pHurt = CheckTraceHullAttack(70, Vector(-16, -16, -16), Vector(16, 16, 16), sk_gonome_dmg_whip.GetFloat(), DMG_SLASH | DMG_ALWAYSGIB);
if ( pHurt )
{
Vector right, up;
AngleVectors( GetAbsAngles(), NULL, &right, &up );
if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
pHurt->ViewPunch(QAngle(random->RandomInt(-20, 20), random->RandomInt(-20, 20), random->RandomInt(-20, 20)));
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + random->RandomInt(1,-1) * (right * 200) );
//pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (up * 100) );
}
}
break;
case GONOME_AE_BLINK:
{
// close eye.
m_nSkin = 1;
}
break;
case GONOME_AE_HOP:
{
float flGravity = sv_gravity.GetFloat();
// throw the squid up into the air on this frame.
if ( GetFlags() & FL_ONGROUND )
{
SetGroundEntity( NULL );
}
// jump into air for 0.8 (24/30) seconds
Vector vecVel = GetAbsVelocity();
vecVel.z += ( 0.625 * flGravity ) * 0.5;
SetAbsVelocity( vecVel );
}
break;
case GONOME_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 )
{
// croonchy bite sound
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Npc_Gonome.Bite" );
// 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 ( pHurt->IsPlayer() )
{
Vector forward, up;
AngleVectors( GetAbsAngles(), &forward, NULL, &up );
if (pHurt->GetFlags() & (FL_NPC | FL_CLIENT))
pHurt->ViewPunch(QAngle(random->RandomInt(-20, 20), random->RandomInt(-20, 20), random->RandomInt(-20, 20)));
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + forward * 300 + up * 300 );
}
}
}
break;
default:
BaseClass::HandleAnimEvent( pEvent );
}
}
int CNPC_Gonome::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 - gonome is a big guy, so has a longer
// melee range than most monsters. This is the tailwhip attack
//=========================================================
int CNPC_Gonome::MeleeAttack1Conditions( float flDot, float flDist )
{
if (GetEnemy()->m_iHealth <= sk_gonome_dmg_whip.GetFloat() && flDist <= 85 && flDot >= 0.7)
{
return ( COND_CAN_MELEE_ATTACK1 );
}
return( COND_NONE );
}
//=========================================================
// MeleeAttack2Conditions - gonome 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_Gonome::MeleeAttack2Conditions( float flDot, float flDist )
{
if ( flDist <= 85 && flDot >= 0.7 && !HasCondition( COND_CAN_MELEE_ATTACK1 ) ) // The player & gonome can be as much as their bboxes
return ( COND_CAN_MELEE_ATTACK2 );
return( COND_NONE );
}
bool CNPC_Gonome::FValidateHintType ( CAI_Hint *pHint )
{
if ( pHint->HintType() == HINT_HL1_WORLD_HUMAN_BLOOD )
return true;
Msg ( "Couldn't validate hint type" );
return false;
}
void CNPC_Gonome::RemoveIgnoredConditions( void )
{
if ( m_flHungryTime > gpGlobals->curtime )
ClearCondition( COND_GONOME_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 )
{
if ( FClassnameIs( GetEnemy(), "npc_citizen" ) ) // BriJee: Citizen is priority target!
ClearCondition( COND_SMELL );
}
if ( GetActivity() == ACT_MELEE_ATTACK1 )
{
ClearCondition( COND_LIGHT_DAMAGE );
ClearCondition( COND_HEAVY_DAMAGE );
}
if ( GetActivity() == ACT_MELEE_ATTACK2 )
{
ClearCondition( COND_LIGHT_DAMAGE );
ClearCondition( COND_HEAVY_DAMAGE );
}
}
Disposition_t CNPC_Gonome::IRelationType( CBaseEntity *pTarget )
{
if ( gpGlobals->curtime - m_flLastHurtTime < 5 && FClassnameIs ( pTarget, "npc_citizen" ) )
{
// 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 );
}
static float DamageForce( const Vector &size, float damage )
{
float force = damage * ((32 * 32 * 72.0) / (size.x * size.y * size.z)) * 5;
if ( force > 1000.0)
{
force = 1000.0;
}
return force;
}
//=========================================================
// TakeDamage - overridden for gonome so we can keep track
// of how much time has passed since it was last injured
//=========================================================
int CNPC_Gonome::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
CTakeDamageInfo info = inputInfo;
if( inputInfo.GetDamageType() & DMG_BURN )
{
// If a zombie is on fire it only takes damage from the fire that's attached to it. (DMG_DIRECT)
// This is to stop zombies from burning to death 10x faster when they're standing around
// 10 fire entities.
if( IsOnFire() && !(inputInfo.GetDamageType() & DMG_DIRECT) )
{
return 0;
}
Scorch( 8, 50 );
Ignite( 100.0f ); //THE FUCK ISN'T THIS WORKING FOR
}
if ( ShouldIgnite( info ) )
{
Ignite( 100.0f );
}
if ( !FClassnameIs ( inputInfo.GetAttacker(), "npc_citizen" ) )
{
// don't forget about headcrabs if it was a headcrab that hurt the squid.
m_flLastHurtTime = gpGlobals->curtime;
}
/*if ( info.GetDamageType() == DMG_BULLET ) //OverMike îòáðàñûâàëî íïñ îò âûñòðåëà
{
Vector vecDir = GetAbsOrigin() - info.GetInflictor()->WorldSpaceCenter();
VectorNormalize( vecDir );
float flForce = DamageForce( WorldAlignSize(), info.GetDamage() );
SetAbsVelocity( GetAbsVelocity() + vecDir * flForce );
info.ScaleDamage( 0.25f );
}*/
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_Gonome::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_Gonome::OnListened( void )
{
AISoundIter_t iter;
CSound *pCurrentSound;
static int conditionsToClear[] =
{
COND_GONOME_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->IsSoundType( SOUND_MEAT | SOUND_CARCASS ) )
{
// the detected scent is a food item
condition = COND_GONOME_SMELL_FOOD;
}
}
if ( condition != COND_NONE )
SetCondition( condition );
pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
}
BaseClass::OnListened();
}
//========================================================
// RunAI - overridden for gonome because there are things
// that need to be checked every think.
//========================================================
void CNPC_Gonome::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() < GONOME_SPRINT_DIST )
{
m_flPlaybackRate = 1.25;
}
}
}
void CNPC_Gonome::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
{
BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
// Set the zombie up to burn to death in about ten seconds.
SetHealth( MIN( m_iHealth, FLAME_DIRECT_DAMAGE_PER_SEC * (ZOMBIE_BURN_TIME + random->RandomFloat( -ZOMBIE_BURN_TIME_NOISE, ZOMBIE_BURN_TIME_NOISE)) ) );
// FIXME: use overlays when they come online
//AddOverlay( ACT_ZOM_WALK_ON_FIRE, false );
Activity activity = GetActivity();
Activity burningActivity = activity;
if ( activity == ACT_WALK )
{
burningActivity = ACT_WALK_ON_FIRE;
}
else if ( activity == ACT_RUN )
{
burningActivity = ACT_RUN_ON_FIRE;
}
else if ( activity == ACT_IDLE )
{
burningActivity = ACT_IDLE_ON_FIRE;
}
if( HaveSequenceForActivity(burningActivity) )
{
// Make sure we have a sequence for this activity (torsos don't have any, for instance)
// to prevent the baseNPC & baseAnimating code from throwing red level errors.
SetActivity( burningActivity );
}
}
//=========================================================
// GetSchedule
//=========================================================
int CNPC_Gonome::SelectSchedule( void )
{
switch ( m_NPCState )
{
case NPC_STATE_ALERT:
{
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
{
return SCHED_GONOME_HURTHOP;
}
if ( HasCondition( COND_GONOME_SMELL_FOOD ) )
{
CSound *pSound;
pSound = GetBestScent();
if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) )
{
// scent is behind or occluded
return SCHED_GONOME_SNIFF_AND_EAT;
}
// food is right out in the open. Just go get it.
return SCHED_GONOME_EAT;
}
if ( HasCondition( COND_SMELL ) )
{
// there's something stinky.
CSound *pSound;
pSound = GetBestScent();
if ( pSound )
return SCHED_GONOME_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_citizen" ) )
{
// this means squid sees a headcrab!
m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime.
return SCHED_GONOME_SEECRAB;
}
else
{
return SCHED_WAKE_ANGRY;
}
}
if ( HasCondition( COND_GONOME_SMELL_FOOD ) )
{
CSound *pSound;
pSound = GetBestScent();
if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) )
{
// scent is behind or occluded
return SCHED_GONOME_SNIFF_AND_EAT;
}
// food is right out in the open. Just go get it.
return SCHED_GONOME_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_Gonome::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;
}
//=========================================================
// FVisible - returns true if a line can be traced from
// the caller's eyes to the target vector
//=========================================================
bool CNPC_Gonome::FVisible ( Vector vecOrigin )
{
trace_t tr;
Vector vecLookerOrigin;
vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
UTIL_TraceLine(vecLookerOrigin, vecOrigin, MASK_BLOCKLOS, this/*pentIgnore*/, COLLISION_GROUP_NONE, &tr);
if ( tr.fraction != 1.0 )
return false; // Line of sight is not established
else
return true;// line of sight is valid.
}
//=========================================================
// Start task - selects the correct activity and performs
// any necessary calculations to start the next task on the
// schedule. OVERRIDDEN for gonome because it needs to
// know explicitly when the last attempt to chase the enemy
// failed, since that impacts its attack choices.
//=========================================================
void CNPC_Gonome::StartTask ( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_MELEE_ATTACK2:
{
CPASAttenuationFilter filter( this );
EmitSound( filter, entindex(), "Npc_Gonome.Growl" );
BaseClass::StartTask ( pTask );
break;
}
case TASK_GONOME_HOPTURN:
{
SetActivity ( ACT_HOP );
if ( GetEnemy() )
{
Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
VectorNormalize( vecFacing );
GetMotor()->SetIdealYaw( vecFacing );
}
break;
}
case TASK_GONOME_EAT:
{
m_flHungryTime = gpGlobals->curtime + pTask->flTaskData;
break;
}
default:
{
BaseClass::StartTask ( pTask );
break;
}
}
}
//=========================================================
// RunTask
//=========================================================
void CNPC_Gonome::RunTask ( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_GONOME_HOPTURN:
{
if ( GetEnemy() )
{
Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
VectorNormalize( vecFacing );
GetMotor()->SetIdealYaw( vecFacing );
}
if ( IsSequenceFinished() )
{
TaskComplete();
}
break;
}
default:
{
BaseClass::RunTask( pTask );
break;
}
}
}
void CNPC_Gonome::Event_Killed(const CTakeDamageInfo &info)
{
if (!cvar->FindVar("oc_npc_zombies_dont_less_headcrab")->GetBool())
{
if ((info.GetDamageType() & (DMG_BLAST)) || (info.GetDamage() >= (m_iMaxHealth * 0.75f))) // BriJee: NEW headcrab release solution
{
Vector vCrabPos;
GetAttachment("headcrab", vCrabPos);
CBaseEntity *pHCrab = NULL;
pHCrab = CBaseEntity::Create("npc_headcrab", /*GetLocalOrigin()*/ vCrabPos, QAngle(0, 0, 0), this);
if (pHCrab)
{
pHCrab->SetGroundEntity(NULL);
pHCrab->AddSpawnFlags(SF_NPC_FALL_TO_GROUND /*SF_HEADCRAB_START_HANGING*/);
}
SetBodygroup(ZOMBIE_BODYGROUP_HEADCRAB, 0); // BriJee: Bodygroup 0 (remove crab)
}
}
if (info.GetDamageType() & DMG_VEHICLE)
{
Vector vecDamageDir = info.GetDamageForce();
VectorNormalize(vecDamageDir);
// Big blood splat
UTIL_BloodSpray(WorldSpaceCenter(), vecDamageDir, BLOOD_COLOR_YELLOW, 8, FX_BLOODSPRAY_CLOUD);
}
BaseClass::Event_Killed(info);
}
//=========================================================
// GetIdealState - Overridden for Gonome to deal with
// the feature that makes it lose interest in headcrabs for
// a while if something injures it.
//=========================================================
NPC_STATE CNPC_Gonome::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_citizen" ) )
{
// 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( monster_gonome, CNPC_Gonome )
DECLARE_TASK ( TASK_GONOME_HOPTURN )
DECLARE_TASK ( TASK_GONOME_EAT )
DECLARE_CONDITION( COND_GONOME_SMELL_FOOD )
DECLARE_ACTIVITY( ACT_GONOME_EXCITED )
DECLARE_ACTIVITY( ACT_GONOME_EAT )
DECLARE_ACTIVITY( ACT_GONOME_DETECT_SCENT )
DECLARE_ACTIVITY( ACT_GONOME_INSPECT_FLOOR )
//=========================================================
// > SCHED_GONOME_HURTHOP
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GONOME_HURTHOP,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SOUND_WAKE 0"
" TASK_GONOME_HOPTURN 0"
" TASK_FACE_ENEMY 0"
" "
" Interrupts"
)
//=========================================================
// > SCHED_GONOME_SEECRAB
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GONOME_SEECRAB,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SOUND_WAKE 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_GONOME_EXCITED"
" TASK_FACE_ENEMY 0"
" "
" Interrupts"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
)
//=========================================================
// > SCHED_GONOME_EAT
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GONOME_EAT,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_GONOME_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_GONOME_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_GONOME_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_GONOME_EAT"
" TASK_GONOME_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_GONOME_SNIFF_AND_EAT
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GONOME_SNIFF_AND_EAT,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_GONOME_EAT 10"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_GONOME_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_GONOME_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_GONOME_EAT"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_GONOME_EAT"
" TASK_GONOME_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_GONOME_WALLOW
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GONOME_WALLOW,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_GONOME_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_GONOME_INSPECT_FLOOR"
" TASK_GONOME_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"
)
//=========================================================
// > SCHED_GONOME_CHASE_ENEMY
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GONOME_CHASE_ENEMY,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
" TASK_GET_PATH_TO_ENEMY 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" "
" Interrupts"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_SMELL"
" COND_CAN_RANGE_ATTACK1"
" COND_CAN_MELEE_ATTACK1"
" COND_CAN_MELEE_ATTACK2"
" COND_TASK_FAILED"
)
AI_END_CUSTOM_NPC()