sqwarmed/sdk_src/game/server/swarm/asw_grub.cpp

647 lines
16 KiB
C++
Raw Normal View History

2024-08-29 19:18:30 -04:00
// A Swarm grub, bursts out of gooey things and crawls about looking icky (non-violent)
#include "cbase.h"
#include "asw_grub.h"
#include "asw_marine.h"
#include "te_effect_dispatch.h"
#include "npc_antlion.h"
#include "npc_bullseye.h"
#include "npcevent.h"
#include "asw_marine.h"
#include "soundenvelope.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define SWARM_GRUB_MODEL "models/swarm/Grubs/Grub.mdl"
const int ASW_GRUB_MIN_JUMP_DIST = 48;
const int ASW_GRUB_MAX_JUMP_DIST = 170;
#define PARASITE_IGNORE_WORLD_COLLISION_TIME 0.5
IMPLEMENT_SERVERCLASS_ST( CASW_Grub, DT_ASW_Grub )
END_SEND_TABLE()
ConVar asw_grub_speedboost( "asw_grub_speedboost", "1.4", FCVAR_CHEAT , "boost speed for the grubs" );
ConVar asw_debug_grubs("asw_debug_grubs", "0", FCVAR_CHEAT, "If set, grubs will print debug messages for various things");
extern ConVar sv_gravity;
int ACT_ASW_GRUB_IDLE;
CASW_Grub::CASW_Grub( void )// : CASW_Alien()
{
m_bMidJump = false;
m_bCommittedToJump = false;
}
LINK_ENTITY_TO_CLASS( asw_grub_advanced, CASW_Grub );
//IMPLEMENT_SERVERCLASS_ST( CASW_Grub, DT_ASW_Grub )
//END_SEND_TABLE()
BEGIN_DATADESC( CASW_Grub )
DEFINE_FIELD( m_bCommittedToJump, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bMidJump, FIELD_BOOLEAN ),
DEFINE_FIELD( m_vecCommittedJumpPos, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_flNextNPCThink, FIELD_TIME ),
DEFINE_FIELD( m_flIgnoreWorldCollisionTime, FIELD_TIME ),
DEFINE_FIELD( m_bJumpFromGoo, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flGooJumpDistance, FIELD_FLOAT ),
DEFINE_FIELD( m_flGooJumpAngle, FIELD_FLOAT ),
DEFINE_THINKFUNC( LeapThink ),
DEFINE_ENTITYFUNC( LeapTouch ),
DEFINE_ENTITYFUNC( NormalTouch ),
END_DATADESC()
enum
{
SCHED_GRUB_JUMP_FROM_GOO = LAST_ASW_ALIEN_SHARED_SCHEDULE,
SCHED_ASW_GRUB_WANDER_ANGRILY,
};
enum
{
TASK_GRUB_JUMP_FROM_GOO = LAST_ASW_ALIEN_SHARED_TASK,
};
extern int AE_HEADCRAB_JUMPATTACK;
void CASW_Grub::Spawn( void )
{
SetModel( SWARM_GRUB_MODEL );
Precache();
BaseClass::Spawn();
SetModel( SWARM_GRUB_MODEL );
SetHullType(HULL_TINY);
SetHullSizeNormal();
SetViewOffset( Vector(6, 0, 11) ) ; // Position of the eyes relative to NPC's origin.
SetNavType( NAV_GROUND );
SetBloodColor( BLOOD_COLOR_GREEN );
m_NPCState = NPC_STATE_NONE;
m_iHealth = 1;
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
if ( m_SquadName != NULL_STRING )
{
CapabilitiesAdd( bits_CAP_SQUAD );
}
SetCollisionGroup( ASW_COLLISION_GROUP_GRUBS );
//SetCollisionGroup( COLLISION_GROUP_DEBRIS );
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
//if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
NPCInit();
BaseClass::Spawn();
SetEfficiency( AIE_EFFICIENT );
SetCollisionGroup( ASW_COLLISION_GROUP_GRUBS );
//RemoveSolidFlags( FSOLID_NOT_STANDABLE );
SetBlocksLOS(false);
AddFlag( FL_FLY );
}
CASW_Grub::~CASW_Grub()
{
}
void CASW_Grub::Precache( void )
{
PrecacheModel( SWARM_GRUB_MODEL );
//PrecacheModel( "Models/Swarm/Grubs/GrubGib1.mdl" );
//PrecacheModel( "Models/Swarm/Grubs/GrubGib2.mdl" );
//PrecacheModel( "Models/Swarm/Grubs/GrubGib3.mdl" );
PrecacheModel( "Models/Swarm/Grubs/GrubGib4.mdl" );
//PrecacheModel( "Models/Swarm/Grubs/GrubGib5.mdl" );
//PrecacheModel( "Models/Swarm/Grubs/GrubGib6.mdl" );
PrecacheScriptSound("ASW_Parasite.Death");
PrecacheScriptSound("ASW_Parasite.Attack");
PrecacheScriptSound("ASW_Parasite.Idle");
BaseClass::Precache();
}
float CASW_Grub::GetIdealSpeed() const
{
return asw_grub_speedboost.GetFloat() * BaseClass::GetIdealSpeed() * m_flPlaybackRate;
}
float CASW_Grub::GetIdealAccel( ) const
{
return GetIdealSpeed() * 1.5f;
}
float CASW_Grub::MaxYawSpeed( void )
{
return 128.0f;
switch ( GetActivity() )
{
case ACT_IDLE:
return 64.0f;
break;
case ACT_WALK:
return 64.0f;
break;
default:
case ACT_RUN:
return 64.0f;
break;
}
return 64.0f;
}
// don't collide with other grubs during jump
bool CASW_Grub::ShouldCollide(int collisionGroup, int contentsMask)
{
/*
if (m_bMidJump)
return false;
if (m_bMidJump &&
(collisionGroup == COLLISION_GROUP_NONE || collisionGroup == ASW_COLLISION_GROUP_GRUBS) )
{
return false;
}
if (collisionGroup == ASW_COLLISION_GROUP_GRUBS)
return false;
*/
return BaseClass::ShouldCollide(collisionGroup, contentsMask);
}
void CASW_Grub::AlertSound()
{
//EmitSound("ASW_Drone.Alert");
//IdleSound();
}
void CASW_Grub::PainSound( const CTakeDamageInfo &info )
{
if (gpGlobals->curtime > m_fNextPainSound)
{
EmitSound("ASW_Parasite.Death");
m_fNextPainSound = gpGlobals->curtime + 0.5f;
}
}
void CASW_Grub::AttackSound()
{
//EmitSound("ASW_Parasite.Attack");
}
void CASW_Grub::IdleSound()
{
}
bool CASW_Grub::CorpseGib( const CTakeDamageInfo &info )
{
CEffectData data;
data.m_vOrigin = WorldSpaceCenter();
data.m_vNormal = data.m_vOrigin - info.GetDamagePosition();
VectorNormalize( data.m_vNormal );
data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 );
data.m_flScale = clamp( data.m_flScale, 1, 3 );
data.m_fFlags = (IsOnFire() || (info.GetDamageType() & DMG_BURN)) ? ASW_GIBFLAG_ON_FIRE : 0;
Msg("grubgib flags: %d burn=%d dt=%d\n", data.m_fFlags, (info.GetDamageType() & DMG_BURN), info.GetDamageType());
DispatchEffect( "GrubGib", data );
//CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, GetAbsOrigin(), 256, 0.5f, this );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Does a jump attack at the given position.
// Input : bRandomJump - Just hop in a random direction.
// vecPos - Position to jump at, ignored if bRandom is set to true.
// bThrown -
//-----------------------------------------------------------------------------
void CASW_Grub::JumpAttack( bool bRandomJump, const Vector &vecPos, bool bThrown )
{
Vector vecJumpVel;
if ( !bRandomJump )
{
float gravity = sv_gravity.GetFloat();
if ( gravity <= 1 )
{
gravity = 1;
}
// How fast does the headcrab need to travel to reach the position given gravity?
float flActualHeight = vecPos.z - GetAbsOrigin().z;
float height = flActualHeight;
if ( height < 16 )
{
height = 60; //16;
}
else
{
float flMaxHeight = bThrown ? 400 : 120;
if ( height > flMaxHeight )
{
height = flMaxHeight;
}
}
// overshoot the jump by an additional 8 inches
// NOTE: This calculation jumps at a position INSIDE the box of the enemy (player)
// so if you make the additional height too high, the crab can land on top of the
// enemy's head. If we want to jump high, we'll need to move vecPos to the surface/outside
// of the enemy's box.
float additionalHeight = 0;
if ( height < 32 )
{
additionalHeight = 8;
}
height += additionalHeight;
// NOTE: This equation here is from vf^2 = vi^2 + 2*a*d
float speed = sqrt( 2 * gravity * height );
float time = speed / gravity;
// add in the time it takes to fall the additional height
// So the impact takes place on the downward slope at the original height
time += sqrt( (2 * additionalHeight) / gravity );
// Scale the sideways velocity to get there at the right time
VectorSubtract( vecPos, GetAbsOrigin(), vecJumpVel );
vecJumpVel /= time;
// Speed to offset gravity at the desired height.
vecJumpVel.z = speed;
// Don't jump too far/fast.
float flJumpSpeed = vecJumpVel.Length();
float flMaxSpeed = bThrown ? 1000.0f : 650.0f;
if ( flJumpSpeed > flMaxSpeed )
{
vecJumpVel *= flMaxSpeed / flJumpSpeed;
}
}
else
{
//
// Jump hop, don't care where.
//
Vector forward, up;
QAngle angFacing = GetLocalAngles();
angFacing.y = m_flGooJumpAngle;
AngleVectors( angFacing, &forward, NULL, &up );
vecJumpVel = Vector( forward.x, forward.y, forward.z) * random->RandomFloat(100, 150); // random->RandomFloat(0, 30)
}
AttackSound();
Leap( vecJumpVel );
}
void CASW_Grub::Leap( const Vector &vecVel )
{
SetTouch( &CASW_Grub::LeapTouch );
SetCondition( COND_FLOATING_OFF_GROUND );
SetGroundEntity( NULL );
m_flIgnoreWorldCollisionTime = gpGlobals->curtime + PARASITE_IGNORE_WORLD_COLLISION_TIME;
if( HasHeadroom() )
{
// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0, 0, 1 ) );
}
SetAbsVelocity( vecVel );
// Think every frame so the player sees the headcrab where he actually is...
m_bMidJump = true;
SetThink( &CASW_Grub::LeapThink );
SetNextThink( gpGlobals->curtime );
//SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
}
void CASW_Grub::LeapThink( void )
{
if (gpGlobals->curtime > m_flNextNPCThink)
{
NPCThink();
m_flNextNPCThink = gpGlobals->curtime + 0.1;
}
if( GetFlags() & FL_ONGROUND )
{
SetThink( &CASW_Grub::CallNPCThink );
SetNextThink( gpGlobals->curtime + 0.1 );
return;
}
SetNextThink( gpGlobals->curtime );
}
void CASW_Grub::NormalTouch(CBaseEntity* pOther)
{
if (!pOther || !pOther->CollisionProp() || pOther->Classify() != CLASS_ASW_MARINE) // only get squashed by marines
return;
// are we within the marine's x/y?
//Vector vecMyFlatPos = GetAbsOrigin();
//vecMyFlatPos.z = pOther->GetAbsOrigin().z;
//if (!CollisionProp()->IsPointInBounds(vecMyFlatPos))
//return;
// are we underneath the marine?
float z_diff = pOther->CollisionProp()->OBBCenter().z - GetAbsOrigin().z;
//float other_half_height = pOther->CollisionProp()->OBBSize().z * 0.5f;
//if (z_diff > other_half_height)
if (pOther->GetAbsOrigin().z > GetAbsOrigin().z) // is he higher than me?
{
// squash this
if (asw_debug_grubs.GetBool())
{
Msg("Squashed by a marine (my z=%f his z=%f diff=%f dist=%f)\n",
GetAbsOrigin().z, pOther->GetAbsOrigin().z, z_diff, pOther->GetAbsOrigin().DistTo(GetAbsOrigin()));
}
Squash(pOther);
}
else
{
if (asw_debug_grubs.GetBool())
{
Msg("Not squashed by a marine (my z=%f his z=%f diff=%f dist=%f)\n",
GetAbsOrigin().z, pOther->GetAbsOrigin().z, z_diff, pOther->GetAbsOrigin().DistTo(GetAbsOrigin()));
}
}
}
void CASW_Grub::Squash(CBaseEntity* pSquasher)
{
Event_Killed( CTakeDamageInfo( pSquasher, pSquasher, 200, DMG_CRUSH ) );
EmitSound( "NPC_AntlionGrub.Squash" );
trace_t tr;
Vector vecDir( 0, 0, -1.0f );
tr.endpos = GetLocalOrigin();
tr.endpos[2] += 8.0f;
MakeDamageBloodDecal( 4, 0.8f, &tr, vecDir );
SetTouch( NULL );
m_iHealth = 0;
CorpseGib( CTakeDamageInfo( pSquasher, pSquasher, 200, DMG_CRUSH ) );
UTIL_Remove(this);
}
//-----------------------------------------------------------------------------
// Purpose: LeapTouch - this is the headcrab's touch function when it is in the air.
// Input : *pOther -
//-----------------------------------------------------------------------------
void CASW_Grub::LeapTouch( CBaseEntity *pOther )
{
m_bMidJump = false;
if ( IRelationType( pOther ) == D_HT )
{
// Don't hit if back on ground
if ( !( GetFlags() & FL_ONGROUND ) )
{
if ( pOther->m_takedamage != DAMAGE_NO )
{
//BiteSound();
//TouchDamage( pOther );
}
else
{
//ImpactSound();
}
}
else
{
//ImpactSound();
}
}
else if( !(GetFlags() & FL_ONGROUND) )
{
// Still in the air...
if( gpGlobals->curtime < m_flIgnoreWorldCollisionTime )
{
// Headcrabs try to ignore the world, static props, and friends for a
// fraction of a second after they jump. This is because they often brush
// doorframes or props as they leap, and touching those objects turns off
// this touch function, which can cause them to hit the player and not bite.
// A timer probably isn't the best way to fix this, but it's one of our
// safer options at this point (sjb).
return;
}
if( !pOther->IsSolid() )
{
// Touching a trigger or something.
return;
}
// don't collide with other grubs
if (pOther->Classify() == CLASS_ASW_GRUB)
return;
// don't collide with marines
if (pOther->Classify() == CLASS_ASW_MARINE)
return;
}
// make sure we're solid
RemoveSolidFlags( FSOLID_NOT_SOLID );
// make it so we collide with various things again
SetCollisionGroup(ASW_COLLISION_GROUP_GRUBS);
m_takedamage = DAMAGE_YES; // doesn't take damage until he's landed
// Shut off the touch function.
SetTouch( &CASW_Grub::NormalTouch );
SetThink ( &CASW_Grub::CallNPCThink );
}
bool CASW_Grub::HasHeadroom()
{
trace_t tr;
UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 1 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
return (tr.fraction == 1.0);
}
void CASW_Grub::StartTask(const Task_t *pTask)
{
switch (pTask->iTask)
{
case TASK_GRUB_JUMP_FROM_GOO:
{
DoJumpFromGoo();
break;
}
default:
{
BaseClass::StartTask( pTask );
}
}
}
void CASW_Grub::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_GRUB_JUMP_FROM_GOO:
GetMotor()->UpdateYaw();
if ( FacingIdeal() )
{
TaskComplete();
}
break;
default:
BaseClass::RunTask( pTask );
break;
}
}
int CASW_Grub::SelectSchedule()
{
if ( m_bJumpFromGoo )
{
m_bJumpFromGoo = false;
return SCHED_GRUB_JUMP_FROM_GOO;
}
return BaseClass::SelectSchedule();
}
void CASW_Grub::SetJumpFromGoo(bool bDoJump, float flJumpAngle, float flJumpDistance)
{
m_bJumpFromGoo = true;
m_flGooJumpDistance = flJumpDistance;
m_flGooJumpAngle = flJumpAngle;
}
void CASW_Grub::DoJumpFromGoo()
{
Vector dir = vec3_origin;
AngleVectors(GetAbsAngles(), &dir);
Vector vecJumpPos = GetAbsOrigin() + dir * m_flGooJumpDistance;
SetActivity( ACT_JUMP );
StudioFrameAdvanceManual( 0.0 );
SetParent( NULL );
RemoveFlag( FL_FLY );
AddEffects( EF_NOINTERP );
GetMotor()->SetIdealYaw( m_flGooJumpAngle );
// do a random jump forward
JumpAttack( true, vecJumpPos, false );
}
void CASW_Grub::IdleInGoo(bool b)
{
if (b)
{
SetActivity((Activity) ACT_ASW_GRUB_IDLE);
}
bDoGooIdle = b;
}
Activity CASW_Grub::TranslateActivity( Activity baseAct, Activity *pIdealWeaponActivity )
{
Activity translated = BaseClass::TranslateActivity(baseAct, pIdealWeaponActivity);
if (translated == ACT_IDLE && bDoGooIdle)
{
return (Activity) ACT_ASW_GRUB_IDLE;
}
return translated;
}
int CASW_Grub::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_IDLE_STAND:
case SCHED_IDLE_WALK:
case SCHED_ALERT_STAND:
case SCHED_ALERT_FACE:
case SCHED_COMBAT_FACE:
return SCHED_ASW_GRUB_WANDER_ANGRILY;
}
return BaseClass::TranslateSchedule( scheduleType );
}
int CASW_Grub::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
{
if ( failedSchedule != SCHED_ASW_GRUB_WANDER_ANGRILY &&
( failedSchedule == SCHED_TAKE_COVER_FROM_ENEMY ||
failedSchedule == SCHED_CHASE_ENEMY_FAILED ) )
{
return SCHED_ASW_GRUB_WANDER_ANGRILY;
}
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
}
// since grubs are just decorative, make sure they're not wasting CPU
void CASW_Grub::UpdateEfficiency( bool bInPVS )
{
SetEfficiency( AIE_VERY_EFFICIENT );
//SetMoveEfficiency( AIME_EFFICIENT );
SetMoveEfficiency( AIME_NORMAL );
}
bool CASW_Grub::BlocksLOS( void )
{
return false;
}
AI_BEGIN_CUSTOM_NPC( asw_grub, CASW_Grub )
DECLARE_ANIMEVENT( AE_HEADCRAB_JUMPATTACK )
DECLARE_TASK( TASK_GRUB_JUMP_FROM_GOO )
DECLARE_ACTIVITY( ACT_ASW_GRUB_IDLE )
DEFINE_SCHEDULE
(
SCHED_GRUB_JUMP_FROM_GOO,
" Tasks"
" TASK_GRUB_JUMP_FROM_GOO 0"
""
" Interrupts"
)
DEFINE_SCHEDULE
(
SCHED_ASW_GRUB_WANDER_ANGRILY,
" Tasks"
" TASK_WANDER 480240" // 48 units to 240 units.
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 4"
""
" Interrupts"
)
AI_END_CUSTOM_NPC()