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

1190 lines
32 KiB
C++

// Swarm Civilian corrupted by SynTek chemicals into being a crazed zombie like thing
#include "cbase.h"
#include "doors.h"
#include "simtimer.h"
#include "npc_BaseZombie.h"
#include "ai_hull.h"
#include "ai_navigator.h"
#include "ai_memory.h"
#include "gib.h"
#include "soundenvelope.h"
#include "engine/IEngineSound.h"
#include "asw_zombie.h"
#include "asw_fx_shared.h"
#include "EntityFlame.h"
#include "asw_zombie.h"
#include "asw_gamerules.h"
#include "asw_burning.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// ACT_FLINCH_PHYSICS
extern ConVar showhitlocation;
ConVar sk_asw_zombie_health( "sk_asw_zombie_health","100");
ConVar asw_zombie_eye_glow("asw_zombie_eye_glow", "1");
// asw - how much extra damage to do to burning aliens
ConVar asw_fire_zombie_damage_scale("asw_fire_zombie_damage_scale", "3.0", FCVAR_CHEAT );
#define ZOMBIE_GLOW_SPRITE "sprites/glow1.vmt"
envelopePoint_t envASWZombieMoanVolumeFast[] =
{
{ 7.0f, 7.0f,
0.1f, 0.1f,
},
{ 0.0f, 0.0f,
0.2f, 0.3f,
},
};
envelopePoint_t envASWZombieMoanVolume[] =
{
{ 1.0f, 1.0f,
0.1f, 0.1f,
},
{ 1.0f, 1.0f,
0.2f, 0.2f,
},
{ 0.0f, 0.0f,
0.3f, 0.4f,
},
};
envelopePoint_t envASWZombieMoanVolumeLong[] =
{
{ 1.0f, 1.0f,
0.3f, 0.5f,
},
{ 1.0f, 1.0f,
0.6f, 1.0f,
},
{ 0.0f, 0.0f,
0.3f, 0.4f,
},
};
envelopePoint_t envASWZombieMoanIgnited[] =
{
{ 1.0f, 1.0f,
0.5f, 1.0f,
},
{ 1.0f, 1.0f,
30.0f, 30.0f,
},
{ 0.0f, 0.0f,
0.5f, 1.0f,
},
};
//=============================================================================
//=============================================================================
LINK_ENTITY_TO_CLASS( asw_zombie, CASW_Zombie );
// asw: no placing just torsos
//LINK_ENTITY_TO_CLASS( asw_zombie_torso, CASW_Zombie );
//---------------------------------------------------------
//---------------------------------------------------------
const char *CASW_Zombie::pASWMoanSounds[] =
{
"ASW_Zombie.Moan1",
"ASW_Zombie.Moan2",
"ASW_Zombie.Moan3",
"ASW_Zombie.Moan4",
};
//=========================================================
// Conditions
//=========================================================
enum
{
COND_ASW_BLOCKED_BY_DOOR = LAST_BASE_ZOMBIE_CONDITION,
COND_ASW_DOOR_OPENED,
COND_ASW_ZOMBIE_CHARGE_TARGET_MOVED,
};
//=========================================================
// Schedules
//=========================================================
enum
{
SCHED_ASW_ZOMBIE_BASH_DOOR = LAST_BASE_ZOMBIE_SCHEDULE,
SCHED_ASW_ZOMBIE_WANDER_ANGRILY,
SCHED_ASW_ZOMBIE_CHARGE_ENEMY,
SCHED_ASW_ZOMBIE_FAIL,
};
//=========================================================
// Tasks
//=========================================================
enum
{
TASK_ASW_ZOMBIE_EXPRESS_ANGER = LAST_BASE_ZOMBIE_TASK,
TASK_ASW_ZOMBIE_YAW_TO_DOOR,
TASK_ASW_ZOMBIE_ATTACK_DOOR,
TASK_ASW_ZOMBIE_CHARGE_ENEMY,
};
//-----------------------------------------------------------------------------
int ACT_ASW_ZOMBIE_TANTRUM;
int ACT_ASW_ZOMBIE_WALLPOUND;
IMPLEMENT_SERVERCLASS_ST(CASW_Zombie, DT_ASW_Zombie)
SendPropBool(SENDINFO(m_bOnFire)),
END_SEND_TABLE()
BEGIN_DATADESC( CASW_Zombie )
DEFINE_FIELD( m_hBlockingDoor, FIELD_EHANDLE ),
DEFINE_FIELD( m_flDoorBashYaw, FIELD_FLOAT ),
DEFINE_EMBEDDED( m_DurationDoorBash ),
DEFINE_EMBEDDED( m_NextTimeToStartDoorBash ),
DEFINE_FIELD( m_vPositionCharged, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_hSpawner, FIELD_EHANDLE ),
DEFINE_FIELD( m_bOnFire, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bHoldoutAlien, FIELD_BOOLEAN ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Zombie::Precache( void )
{
BaseClass::Precache();
PrecacheModel( "models/Swarm/CivilianZ/CivilianZ.mdl" );
PrecacheModel( "models/zombie/classic_torso.mdl" );
PrecacheModel( "models/zombie/classic_legs.mdl" );
PrecacheScriptSound( "Zombie.FootstepRight" );
PrecacheScriptSound( "Zombie.FootstepLeft" );
PrecacheScriptSound( "Zombie.FootstepLeft" );
PrecacheScriptSound( "Zombie.ScuffRight" );
PrecacheScriptSound( "Zombie.ScuffLeft" );
PrecacheScriptSound( "Zombie.AttackHit" );
PrecacheScriptSound( "Zombie.AttackMiss" );
PrecacheScriptSound( "Zombie.Pain" );
PrecacheScriptSound( "Zombie.Die" );
PrecacheScriptSound( "Zombie.Alert" );
PrecacheScriptSound( "Zombie.Idle" );
PrecacheScriptSound( "Zombie.Attack" );
PrecacheScriptSound( "NPC_BaseZombie.Moan1" );
PrecacheScriptSound( "NPC_BaseZombie.Moan2" );
PrecacheScriptSound( "NPC_BaseZombie.Moan3" );
PrecacheScriptSound( "NPC_BaseZombie.Moan4" );
PrecacheScriptSound( "ASW_Zombie.Pain" );
PrecacheScriptSound( "ASW_Zombie.Die" );
PrecacheScriptSound( "ASW_Zombie.Alert" );
PrecacheScriptSound( "ASW_Zombie.Idle" );
PrecacheScriptSound( "ASW_Zombie.Attack" );
PrecacheScriptSound( "ASW_Zombie.Moan1" );
PrecacheScriptSound( "ASW_Zombie.Moan2" );
PrecacheScriptSound( "ASW_Zombie.Moan3" );
PrecacheScriptSound( "ASW_Zombie.Moan4" );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CASW_Zombie::Spawn( void )
{
Precache();
// asw: no placing just torsos
//if( FClassnameIs( this, "asw_zombie" ) )
//{
m_fIsTorso = false;
//}
//else
//{
// This was placed as an npc_zombie_torso
//m_fIsTorso = true;
//}
m_fIsHeadless = false;
SetBloodColor( BLOOD_COLOR_RED );
SetHealthByDifficultyLevel();
m_flFieldOfView = 0.2;
SetDistLook( 768.0f );
m_flDistTooFar = 1024.0f;
if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) )
{
m_flDistTooFar = 2248.0f;
SetDistLook( 1200.0f );
}
CapabilitiesClear();
//GetNavigator()->SetRememberStaleNodes( false );
BaseClass::Spawn();
m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 1.0, 4.0 );
if (asw_zombie_eye_glow.GetBool())
CreateEyeGlows();
}
void CASW_Zombie::UpdateOnRemove( void )
{
RemoveEyeGlows(0.0f);
BaseClass::UpdateOnRemove();
}
// give our zombies 360 degree vision
bool CASW_Zombie::FInViewCone( const Vector &vecSpot )
{
return true;
}
// player (and player controlled marines) always avoid zombies
bool CASW_Zombie::ShouldPlayerAvoid( void )
{
return true;
}
float CASW_Zombie::GetIdealSpeed() const
{
return BaseClass::GetIdealSpeed() * m_flPlaybackRate;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CASW_Zombie::PrescheduleThink( void )
{
if( gpGlobals->curtime > m_flNextMoanSound )
{
if( CanPlayMoanSound() )
{
// Classic guy idles instead of moans.
IdleSound();
m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 2.0, 5.0 );
}
else
{
m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 1.0, 2.0 );
}
}
BaseClass::PrescheduleThink();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CASW_Zombie::SelectSchedule ( void )
{
if( HasCondition( COND_PHYSICS_DAMAGE ) && !m_ActBusyBehavior.IsActive() )
{
return SCHED_FLINCH_PHYSICS;
}
return BaseClass::SelectSchedule();
}
//-----------------------------------------------------------------------------
// Purpose: Sound of a footstep
//-----------------------------------------------------------------------------
void CASW_Zombie::FootstepSound( bool fRightFoot )
{
if( fRightFoot )
{
EmitSound( "Zombie.FootstepRight" );
}
else
{
EmitSound( "Zombie.FootstepLeft" );
}
}
//-----------------------------------------------------------------------------
// Purpose: Sound of a foot sliding/scraping
//-----------------------------------------------------------------------------
void CASW_Zombie::FootscuffSound( bool fRightFoot )
{
if( fRightFoot )
{
EmitSound( "Zombie.ScuffRight" );
}
else
{
EmitSound( "Zombie.ScuffLeft" );
}
}
//-----------------------------------------------------------------------------
// Purpose: Play a random attack hit sound
//-----------------------------------------------------------------------------
void CASW_Zombie::AttackHitSound( void )
{
EmitSound( "Zombie.AttackHit" );
}
//-----------------------------------------------------------------------------
// Purpose: Play a random attack miss sound
//-----------------------------------------------------------------------------
void CASW_Zombie::AttackMissSound( void )
{
// Play a random attack miss sound
EmitSound( "Zombie.AttackMiss" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Zombie::PainSound( const CTakeDamageInfo &info )
{
// We're constantly taking damage when we are on fire. Don't make all those noises!
if ( IsOnFire() )
{
return;
}
EmitSound( "ASW_Zombie.Pain" );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CASW_Zombie::DeathSound()
{
EmitSound( "ASW_Zombie.Die" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CASW_Zombie::AlertSound( void )
{
EmitSound( "ASW_Zombie.Alert" );
// Don't let a moan sound cut off the alert sound.
m_flNextMoanSound += random->RandomFloat( 2.0, 4.0 );
}
//-----------------------------------------------------------------------------
// Purpose: Returns a moan sound for this class of zombie.
//-----------------------------------------------------------------------------
const char *CASW_Zombie::GetMoanSound( int nSound )
{
return pASWMoanSounds[ nSound % ARRAYSIZE( pASWMoanSounds ) ];
}
//-----------------------------------------------------------------------------
// Purpose: Play a random idle sound.
//-----------------------------------------------------------------------------
void CASW_Zombie::IdleSound( void )
{
if( GetState() == NPC_STATE_IDLE && random->RandomFloat( 0, 1 ) == 0 )
{
// Moan infrequently in IDLE state.
return;
}
if( IsSlumped() )
{
// Sleeping zombies are quiet.
return;
}
EmitSound( "ASW_Zombie.Idle" );
}
//-----------------------------------------------------------------------------
// Purpose: Play a random attack sound.
//-----------------------------------------------------------------------------
void CASW_Zombie::AttackSound( void )
{
EmitSound( "ASW_Zombie.Attack" );
}
//-----------------------------------------------------------------------------
// Purpose: Returns the classname (ie "npc_headcrab") to spawn when our headcrab bails.
//-----------------------------------------------------------------------------
const char *CASW_Zombie::GetHeadcrabClassname( void )
{
return "npc_headcrab";
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char *CASW_Zombie::GetHeadcrabModel( void )
{
return "models/headcrabclassic.mdl";
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *CASW_Zombie::GetLegsModel( void )
{
return "models/zombie/classic_legs.mdl";
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const char *CASW_Zombie::GetTorsoModel( void )
{
return "models/zombie/classic_torso.mdl";
}
//---------------------------------------------------------
//---------------------------------------------------------
void CASW_Zombie::SetZombieModel( void )
{
Hull_t lastHull = GetHullType();
// asw: no placing just torsos
//if ( m_fIsTorso )
//{
//SetModel( "models/zombie/classic_torso.mdl" );
//SetHullType( HULL_TINY );
//}
//else
//{
SetModel( "models/Swarm/CivilianZ/CivilianZ.mdl" );
SetHullType( HULL_HUMAN );
//}
//SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless );
SetHullSizeNormal( true );
SetDefaultEyeOffset();
SetActivity( ACT_IDLE );
// hull changed size, notify vphysics
// UNDONE: Solve this generally, systematically so other
// NPCs can change size
if ( lastHull != GetHullType() )
{
if ( VPhysicsGetObject() )
{
SetupVPhysicsHull();
}
}
}
//---------------------------------------------------------
// Classic zombie only uses moan sound if on fire.
//---------------------------------------------------------
void CASW_Zombie::MoanSound( envelopePoint_t *pEnvelope, int iEnvelopeSize )
{
if( IsOnFire() )
{
BaseClass::MoanSound( pEnvelope, iEnvelopeSize );
}
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CASW_Zombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold )
{
// asw: no placing just torsos
return false;
if( IsSlumped() )
{
// Never break apart a slouched zombie. This is because the most fun
// slouched zombies to kill are ones sleeping leaning against explosive
// barrels. If you break them in half in the blast, the force of being
// so close to the explosion makes the body pieces fly at ridiculous
// velocities because the pieces weigh less than the whole.
return false;
}
return BaseClass::ShouldBecomeTorso( info, flDamageThreshold );
}
//---------------------------------------------------------
//---------------------------------------------------------
void CASW_Zombie::GatherConditions( void )
{
BaseClass::GatherConditions();
static int conditionsToClear[] =
{
COND_ASW_BLOCKED_BY_DOOR,
COND_ASW_DOOR_OPENED,
COND_ASW_ZOMBIE_CHARGE_TARGET_MOVED,
};
ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
if ( m_hBlockingDoor == NULL ||
( m_hBlockingDoor->m_toggle_state == TS_AT_TOP ||
m_hBlockingDoor->m_toggle_state == TS_GOING_UP ) )
{
ClearCondition( COND_ASW_BLOCKED_BY_DOOR );
if ( m_hBlockingDoor != NULL )
{
SetCondition( COND_ASW_DOOR_OPENED );
m_hBlockingDoor = NULL;
}
}
else
SetCondition( COND_ASW_BLOCKED_BY_DOOR );
if ( ConditionInterruptsCurSchedule( COND_ASW_ZOMBIE_CHARGE_TARGET_MOVED ) )
{
if ( GetNavigator()->IsGoalActive() )
{
const float CHARGE_RESET_TOLERANCE = 60.0;
if ( !GetEnemy() ||
( m_vPositionCharged - GetEnemyLKP() ).Length() > CHARGE_RESET_TOLERANCE )
{
SetCondition( COND_ASW_ZOMBIE_CHARGE_TARGET_MOVED );
}
}
}
}
//---------------------------------------------------------
//---------------------------------------------------------
int CASW_Zombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
{
if ( HasCondition( COND_ASW_BLOCKED_BY_DOOR ) && m_hBlockingDoor != NULL )
{
ClearCondition( COND_ASW_BLOCKED_BY_DOOR );
if ( m_NextTimeToStartDoorBash.Expired() && failedSchedule != SCHED_ASW_ZOMBIE_BASH_DOOR )
return SCHED_ASW_ZOMBIE_BASH_DOOR;
m_hBlockingDoor = NULL;
}
if ( failedSchedule != SCHED_ASW_ZOMBIE_CHARGE_ENEMY &&
IsPathTaskFailure( taskFailCode ) &&
random->RandomInt( 1, 100 ) < 50 )
{
return SCHED_ASW_ZOMBIE_CHARGE_ENEMY;
}
if ( failedSchedule != SCHED_ASW_ZOMBIE_WANDER_ANGRILY &&
( failedSchedule == SCHED_TAKE_COVER_FROM_ENEMY ||
failedSchedule == SCHED_CHASE_ENEMY_FAILED ) )
{
return SCHED_ASW_ZOMBIE_WANDER_ANGRILY;
}
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
}
//---------------------------------------------------------
//---------------------------------------------------------
int CASW_Zombie::TranslateSchedule( int scheduleType )
{
if ( scheduleType == SCHED_COMBAT_FACE && IsUnreachable( GetEnemy() ) )
return SCHED_TAKE_COVER_FROM_ENEMY;
if ( !m_fIsTorso && scheduleType == SCHED_FAIL )
return SCHED_ASW_ZOMBIE_FAIL;
return BaseClass::TranslateSchedule( scheduleType );
}
//---------------------------------------------------------
Activity CASW_Zombie::NPC_TranslateActivity( Activity newActivity )
{
newActivity = BaseClass::NPC_TranslateActivity( newActivity );
if ( newActivity == ACT_RUN )
return ACT_WALK;
return newActivity;
}
//---------------------------------------------------------
//---------------------------------------------------------
void CASW_Zombie::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
{
BaseClass::OnStateChange( OldState, NewState );
}
//---------------------------------------------------------
//---------------------------------------------------------
void CASW_Zombie::StartTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_ASW_ZOMBIE_EXPRESS_ANGER:
{
if ( random->RandomInt( 1, 4 ) == 2 )
{
SetIdealActivity( (Activity)ACT_ASW_ZOMBIE_TANTRUM );
}
else
{
TaskComplete();
}
break;
}
case TASK_ASW_ZOMBIE_YAW_TO_DOOR:
{
AssertMsg( m_hBlockingDoor != NULL, "Expected condition handling to break schedule before landing here" );
if ( m_hBlockingDoor != NULL )
{
GetMotor()->SetIdealYaw( m_flDoorBashYaw );
}
TaskComplete();
break;
}
case TASK_ASW_ZOMBIE_ATTACK_DOOR:
{
m_DurationDoorBash.Reset();
SetIdealActivity( SelectDoorBash() );
break;
}
case TASK_ASW_ZOMBIE_CHARGE_ENEMY:
{
if ( !GetEnemy() )
TaskFail( FAIL_NO_ENEMY );
else if ( GetNavigator()->SetVectorGoalFromTarget( GetEnemy()->GetLocalOrigin() ) )
{
m_vPositionCharged = GetEnemy()->GetLocalOrigin();
TaskComplete();
}
else
TaskFail( FAIL_NO_ROUTE );
break;
}
default:
BaseClass::StartTask( pTask );
break;
}
}
//---------------------------------------------------------
//---------------------------------------------------------
void CASW_Zombie::RunTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_ASW_ZOMBIE_ATTACK_DOOR:
{
if ( IsActivityFinished() )
{
if ( m_DurationDoorBash.Expired() )
{
TaskComplete();
m_NextTimeToStartDoorBash.Reset();
}
else
ResetIdealActivity( SelectDoorBash() );
}
break;
}
case TASK_ASW_ZOMBIE_CHARGE_ENEMY:
{
break;
}
case TASK_ASW_ZOMBIE_EXPRESS_ANGER:
{
if ( IsActivityFinished() )
{
TaskComplete();
}
break;
}
default:
BaseClass::RunTask( pTask );
break;
}
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CASW_Zombie::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor,
float distClear, AIMoveResult_t *pResult )
{
if ( BaseClass::OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
{
if ( IsMoveBlocked( *pResult ) && pMoveGoal->directTrace.vHitNormal != vec3_origin )
{
m_hBlockingDoor = pDoor;
m_flDoorBashYaw = UTIL_VecToYaw( pMoveGoal->directTrace.vHitNormal * -1 );
}
return true;
}
return false;
}
//---------------------------------------------------------
//---------------------------------------------------------
Activity CASW_Zombie::SelectDoorBash()
{
if ( random->RandomInt( 1, 3 ) == 1 )
return ACT_MELEE_ATTACK1;
return (Activity)ACT_ASW_ZOMBIE_WALLPOUND;
}
//---------------------------------------------------------
// Zombies should scream continuously while burning, so long
// as they are alive.
//---------------------------------------------------------
void CASW_Zombie::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
{
return; // asw asw_ignite instead
/*
if( !IsOnFire() && IsAlive() )
{
BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
RemoveSpawnFlags( SF_NPC_GAG );
MoanSound( envASWZombieMoanIgnited, ARRAYSIZE( envASWZombieMoanIgnited ) );
if( m_pMoanSound )
{
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, 120, 1.0 );
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1, 1.0 );
}
}*/
}
//---------------------------------------------------------
//---------------------------------------------------------
int CASW_Zombie::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
#ifndef HL2_EPISODIC
if ( info.GetDamageType() & DMG_BUCKSHOT )
{
if( !m_fIsTorso && info.GetDamage() > (m_iMaxHealth/3) )
{
// Always flinch if damaged a lot by buckshot, even if not shot in the head.
// The reason for making sure we did at least 1/3rd of the zombie's max health
// is so the zombie doesn't flinch every time the odd shotgun pellet hits them,
// and so the maximum number of times you'll see a zombie flinch like this is 2.(sjb)
AddGesture( ACT_GESTURE_FLINCH_HEAD );
}
}
#endif // HL2_EPISODIC
// catching on fire
int result = 0;
// scale burning damage up
if (dynamic_cast<CEntityFlame*>(info.GetAttacker()))
{
CTakeDamageInfo newDamage = info;
newDamage.ScaleDamage(asw_fire_zombie_damage_scale.GetFloat());
result = BaseClass::OnTakeDamage_Alive(newDamage);
}
else
{
result = BaseClass::OnTakeDamage_Alive(info);
}
// if we take fire damage, catch on fire
if (result > 0 && (info.GetDamageType() & DMG_BURN))
ASW_Ignite(30.0f, 0, info.GetAttacker());
//CASW_Marine* pMarine = dynamic_cast<CASW_Marine*>(info.GetAttacker());
//if (pMarine)
//pMarine->HurtAlien(this);
return BaseClass::OnTakeDamage_Alive( info );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CASW_Zombie::IsHeavyDamage( const CTakeDamageInfo &info )
{
#ifdef HL2_EPISODIC
if ( info.GetDamageType() & DMG_BUCKSHOT )
{
if ( !m_fIsTorso && info.GetDamage() > (m_iMaxHealth/3) )
return true;
}
// Randomly treat all damage as heavy
if ( info.GetDamageType() & (DMG_BULLET | DMG_BUCKSHOT) )
{
// Don't randomly flinch if I'm melee attacking
if ( !HasCondition(COND_CAN_MELEE_ATTACK1) && (RandomFloat() > 0.5) )
{
// Randomly forget I've flinched, so that I'll be forced to play a big flinch
// If this doesn't happen, it means I may not fully flinch if I recently flinched
if ( RandomFloat() > 0.75 )
{
Forget(bits_MEMORY_FLINCHED);
}
return true;
}
}
#endif // HL2_EPISODIC
return BaseClass::IsHeavyDamage(info);
}
//---------------------------------------------------------
//---------------------------------------------------------
void CASW_Zombie::BuildScheduleTestBits( void )
{
BaseClass::BuildScheduleTestBits();
if( !m_fIsTorso && !IsCurSchedule( SCHED_FLINCH_PHYSICS ) && !m_ActBusyBehavior.IsActive() )
{
SetCustomInterruptCondition( COND_PHYSICS_DAMAGE );
}
}
HeadcrabRelease_t CASW_Zombie::ShouldReleaseHeadcrab( const CTakeDamageInfo &info, float flDamageThreshold )
{
return RELEASE_NO;
}
void CASW_Zombie::StudioFrameAdvance()
{
// vary out playback rate for crazy zombie-ness
//m_flPlaybackRate = RandomFloat(2.0f, 3.0f);
m_flPlaybackRate = 1.5f;
BaseClass::StudioFrameAdvance();
}
// make the bleeding more pronounced
void CASW_Zombie::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
m_fNoDamageDecal = false;
if ( m_takedamage == DAMAGE_NO )
return;
CTakeDamageInfo subInfo = info;
SetLastHitGroup( ptr->hitgroup );
m_nForceBone = ptr->physicsbone; // save this bone for physics forces
Assert( m_nForceBone > -255 && m_nForceBone < 256 );
bool bDebug = showhitlocation.GetBool();
switch ( ptr->hitgroup )
{
case HITGROUP_GENERIC:
if( bDebug ) DevMsg("Hit Location: Generic\n");
break;
// hit gear, react but don't bleed
case HITGROUP_GEAR:
subInfo.SetDamage( 0.01 );
ptr->hitgroup = HITGROUP_GENERIC;
if( bDebug ) DevMsg("Hit Location: Gear\n");
break;
case HITGROUP_HEAD:
subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
if( bDebug ) DevMsg("Hit Location: Head\n");
break;
case HITGROUP_CHEST:
subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
if( bDebug ) DevMsg("Hit Location: Chest\n");
break;
case HITGROUP_STOMACH:
subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
if( bDebug ) DevMsg("Hit Location: Stomach\n");
break;
case HITGROUP_LEFTARM:
case HITGROUP_RIGHTARM:
subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
if( bDebug ) DevMsg("Hit Location: Left/Right Arm\n");
break
;
case HITGROUP_LEFTLEG:
case HITGROUP_RIGHTLEG:
subInfo.ScaleDamage( GetHitgroupDamageMultiplier(ptr->hitgroup, info) );
if( bDebug ) DevMsg("Hit Location: Left/Right Leg\n");
break;
default:
if( bDebug ) DevMsg("Hit Location: UNKNOWN\n");
break;
}
if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) )
{
if( !IsPlayer() || ( IsPlayer() && gpGlobals->maxClients > 1 ) )
{
// NPC's always bleed. Players only bleed in multiplayer.
//SpawnBlood( ptr->endpos, vecDir, BloodColor(), subInfo.GetDamage() );// a little surface blood.
//UTIL_ASW_DroneBleed( ptr->endpos, vecDir, 4 );
UTIL_ASW_BloodDrips( GetAbsOrigin()+Vector(0,0,60)+vecDir*3, vecDir, BloodColor(), 5 );
}
TraceBleed( subInfo.GetDamage(), vecDir, ptr, subInfo.GetDamageType() );
if ( ptr->hitgroup == HITGROUP_HEAD && m_iHealth - subInfo.GetDamage() > 0 )
{
m_fNoDamageDecal = true;
}
}
// Airboat gun will impart major force if it's about to kill him....
if ( info.GetDamageType() & DMG_AIRBOAT )
{
if ( subInfo.GetDamage() >= GetHealth() )
{
float flMagnitude = subInfo.GetDamageForce().Length();
if ( (flMagnitude != 0.0f) && (flMagnitude < 400.0f * 65.0f) )
{
subInfo.ScaleDamageForce( 400.0f * 65.0f / flMagnitude );
}
}
}
if( info.GetInflictor() )
{
subInfo.SetInflictor( info.GetInflictor() );
}
else
{
subInfo.SetInflictor( info.GetAttacker() );
}
AddMultiDamage( subInfo, this );
}
void CASW_Zombie::OnRestore()
{
BaseClass::OnRestore();
CreateEyeGlows();
}
void CASW_Zombie::CreateEyeGlows( void )
{
//Create our Eye sprites
if ( m_pEyeGlow[0] == NULL )
{
m_pEyeGlow[0] = CSprite::SpriteCreate( ZOMBIE_GLOW_SPRITE, GetLocalOrigin(), false );
m_pEyeGlow[0]->SetAttachment( this, LookupAttachment( "LeftEye" ) );
m_pEyeGlow[0]->SetTransparency( kRenderTransAdd, 128, 0, 0, 128, kRenderFxNoDissipation );
m_pEyeGlow[0]->SetBrightness( 164, 0.1f );
m_pEyeGlow[0]->SetScale( 0.1f, 0.1f );
m_pEyeGlow[0]->SetColor( 128, 0, 0 );
m_pEyeGlow[0]->SetAsTemporary();
}
if ( m_pEyeGlow[1] == NULL )
{
m_pEyeGlow[1] = CSprite::SpriteCreate( ZOMBIE_GLOW_SPRITE, GetLocalOrigin(), false );
m_pEyeGlow[1]->SetAttachment( this, LookupAttachment( "RightEye" ) );
m_pEyeGlow[1]->SetTransparency( kRenderTransAdd, 128, 0, 0, 128, kRenderFxNoDissipation );
m_pEyeGlow[1]->SetBrightness( 164, 0.1f );
m_pEyeGlow[1]->SetScale( 0.1f, 0.1f );
m_pEyeGlow[1]->SetColor( 128, 0, 0 );
m_pEyeGlow[1]->SetAsTemporary();
}
}
void CASW_Zombie::RemoveEyeGlows( float flDelay )
{
if( m_pEyeGlow[0] )
{
m_pEyeGlow[0]->FadeAndDie( flDelay );
m_pEyeGlow[0] = NULL;
}
if( m_pEyeGlow[1] )
{
m_pEyeGlow[1]->FadeAndDie( flDelay );
m_pEyeGlow[1] = NULL;
}
}
void CASW_Zombie::Event_Killed( const CTakeDamageInfo &info )
{
RemoveEyeGlows( 0.0f );
BaseClass::Event_Killed( info );
}
void CASW_Zombie::SetSpawner(CASW_Base_Spawner* spawner)
{
m_hSpawner = spawner;
}
void CASW_Zombie::ASW_Ignite( float flFlameLifetime, float flSize, CBaseEntity *pAttacker, CBaseEntity *pDamagingWeapon /*= NULL */ )
{
if (AllowedToIgnite())
{
if( IsOnFire() )
return;
m_bOnFire = true;
if (ASWBurning())
ASWBurning()->BurnEntity(this, pAttacker, flFlameLifetime, 0.4f, 5.0f * 0.4f); // 5 dps, applied every 0.4 seconds
/*
CEntityFlame *pFlame = CEntityFlame::Create( this );
if (pFlame)
{
if (pAttacker)
pFlame->SetOwnerEntity(pAttacker);
pFlame->SetLifetime( flFlameLifetime );
AddFlag( FL_ONFIRE );
SetEffectEntity( pFlame );
if ( flSize > 0.0f )
{
pFlame->SetSize( flSize );
}
}
*/
m_OnIgnite.FireOutput( this, this );
RemoveSpawnFlags( SF_NPC_GAG );
MoanSound( envASWZombieMoanIgnited, ARRAYSIZE( envASWZombieMoanIgnited ) );
if( m_pMoanSound )
{
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, 120, 1.0 );
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 1, 1.0 );
}
}
}
void CASW_Zombie::Extinguish()
{
m_bOnFire = false;
if( m_pMoanSound )
{
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pMoanSound, 0, 2.0 );
ENVELOPE_CONTROLLER.SoundChangePitch( m_pMoanSound, 100, 2.0 );
m_flNextMoanSound = gpGlobals->curtime + random->RandomFloat( 2.0, 4.0 );
}
if (ASWBurning())
ASWBurning()->Extinguish(this);
RemoveFlag( FL_ONFIRE );
}
void CASW_Zombie::SetHealthByDifficultyLevel()
{
SetHealth(ASWGameRules()->ModifyAlienHealthBySkillLevel(sk_asw_zombie_health.GetInt()));
}
//=============================================================================
AI_BEGIN_CUSTOM_NPC( asw_zombie, CASW_Zombie )
DECLARE_CONDITION( COND_ASW_BLOCKED_BY_DOOR )
DECLARE_CONDITION( COND_ASW_DOOR_OPENED )
DECLARE_CONDITION( COND_ASW_ZOMBIE_CHARGE_TARGET_MOVED )
DECLARE_TASK( TASK_ASW_ZOMBIE_EXPRESS_ANGER )
DECLARE_TASK( TASK_ASW_ZOMBIE_YAW_TO_DOOR )
DECLARE_TASK( TASK_ASW_ZOMBIE_ATTACK_DOOR )
DECLARE_TASK( TASK_ASW_ZOMBIE_CHARGE_ENEMY )
DECLARE_ACTIVITY( ACT_ASW_ZOMBIE_TANTRUM );
DECLARE_ACTIVITY( ACT_ASW_ZOMBIE_WALLPOUND );
DEFINE_SCHEDULE
(
SCHED_ASW_ZOMBIE_BASH_DOOR,
" Tasks"
" TASK_SET_ACTIVITY ACTIVITY:ACT_ASW_ZOMBIE_TANTRUM"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
" TASK_ASW_ZOMBIE_YAW_TO_DOOR 0"
" TASK_FACE_IDEAL 0"
" TASK_ASW_ZOMBIE_ATTACK_DOOR 0"
""
" Interrupts"
" COND_ZOMBIE_RELEASECRAB"
" COND_ENEMY_DEAD"
" COND_NEW_ENEMY"
" COND_ASW_DOOR_OPENED"
)
DEFINE_SCHEDULE
(
SCHED_ASW_ZOMBIE_WANDER_ANGRILY,
" Tasks"
" TASK_WANDER 480240" // 48 units to 240 units.
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 4"
""
" Interrupts"
" COND_ZOMBIE_RELEASECRAB"
" COND_ENEMY_DEAD"
" COND_NEW_ENEMY"
" COND_ASW_DOOR_OPENED"
)
DEFINE_SCHEDULE
(
SCHED_ASW_ZOMBIE_CHARGE_ENEMY,
" Tasks"
" TASK_ASW_ZOMBIE_CHARGE_ENEMY 0"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_MELEE_ATTACK1" /* placeholder until frustration/rage/fence shake animation available */
""
" Interrupts"
" COND_ZOMBIE_RELEASECRAB"
" COND_ENEMY_DEAD"
" COND_NEW_ENEMY"
" COND_ASW_DOOR_OPENED"
" COND_ASW_ZOMBIE_CHARGE_TARGET_MOVED"
)
DEFINE_SCHEDULE
(
SCHED_ASW_ZOMBIE_FAIL,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_ASW_ZOMBIE_TANTRUM"
" TASK_WAIT 1"
" TASK_WAIT_PVS 0"
""
" Interrupts"
" COND_CAN_RANGE_ATTACK1 "
" COND_CAN_RANGE_ATTACK2 "
" COND_CAN_MELEE_ATTACK1 "
" COND_CAN_MELEE_ATTACK2"
" COND_GIVE_WAY"
" COND_ASW_DOOR_OPENED"
)
AI_END_CUSTOM_NPC()
//=============================================================================