1101 lines
31 KiB
C++
1101 lines
31 KiB
C++
#include "cbase.h"
|
|
#include "asw_shieldbug.h"
|
|
#include "asw_marine.h"
|
|
#include "asw_shareddefs.h"
|
|
#include "npc_bullseye.h"
|
|
#include "npcevent.h"
|
|
#include "asw_marine.h"
|
|
#include "asw_game_resource.h"
|
|
#include "asw_marine_resource.h"
|
|
#include "soundenvelope.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "asw_fx_shared.h"
|
|
#include "asw_util_shared.h"
|
|
#include "IEffects.h"
|
|
#include "asw_marine_speech.h"
|
|
#include "asw_gamerules.h"
|
|
#include "asw_weapon_assault_shotgun_shared.h"
|
|
#include "te.h"
|
|
#include "props_shared.h"
|
|
#include "asw_fail_advice.h"
|
|
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// asw todo: push any blocking sparks out to the claws/head, even if the damage hits inside
|
|
|
|
#define ASW_SHIELDBUG_MELEE1_START_ATTACK_RANGE 100.0f
|
|
#define ASW_SHIELDBUG_MELEE1_RANGE 100.0f
|
|
#define ASW_SHIELDBUG_FLINCH_INTERVAL 2.0f
|
|
#define ASW_SHIELDBUG_TRANSITION_SPEED 1600.0f
|
|
|
|
// activities
|
|
int ACT_SHIELDBUG_PRE_LEAP;
|
|
int ACT_START_DEFENDING;
|
|
int ACT_LEAVE_DEFENDING;
|
|
int ACT_RUN_DEFEND;
|
|
int ACT_IDLE_DEFEND;
|
|
int ACT_FLINCH_LEFTARM_DEFEND;
|
|
int ACT_FLINCH_RIGHTARM_DEFEND;
|
|
int ACT_MELEE_ATTACK1_DEFEND;
|
|
int ACT_TURN_LEFT_DEFEND;
|
|
int ACT_TURN_RIGHT_DEFEND;
|
|
int ACT_ALIEN_SHOVER_ROAR_DEFEND;
|
|
|
|
// sb anim events
|
|
int AE_SHIELDBUG_WALK_FOOTSTEP;
|
|
int AE_SHIELDBUG_FOOTSTEP_SOFT;
|
|
int AE_SHIELDBUG_FOOTSTEP_HEAVY;
|
|
int AE_SHIELDBUG_MELEE_HIT1;
|
|
int AE_SHIELDBUG_MELEE_HIT2;
|
|
int AE_SHIELDBUG_MELEE1_SOUND;
|
|
int AE_SHIELDBUG_MELEE2_SOUND;
|
|
int AE_SHIELDBUG_MOUTH_BLEED;
|
|
int AE_SHIELDBUG_ALERT_SOUND;
|
|
int AE_SHIELDBUG_SCREENSHAKE;
|
|
int AE_SHIELDBUG_START_DEFEND;
|
|
int AE_SHIELDBUG_LEAVE_DEFEND;
|
|
|
|
ConVar sk_asw_shieldbug_damage( "sk_asw_shieldbug_damage", "25.0", FCVAR_CHEAT, "Damage per swipe from the shieldbug");
|
|
ConVar asw_shieldbug_speedboost( "asw_shieldbug_speedboost", "1.0",FCVAR_CHEAT , "boost speed for the shieldbug" );
|
|
ConVar asw_shieldbug_defending_speedboost( "asw_shieldbug_defending_speedboost", "1.0", FCVAR_CHEAT, "Boost speed for the shieldbug when defending");
|
|
ConVar asw_debug_shieldbug("asw_debug_shieldbug", "0", FCVAR_CHEAT, "Display shieldbug debug messages");
|
|
ConVar asw_shieldbug_screen_shake("asw_shieldbug_screen_shake", "1", FCVAR_CHEAT, "Should the shieldbug cause screen shake?");
|
|
ConVar asw_shieldbug_melee_force("asw_shieldbug_melee_force", "2.0", FCVAR_CHEAT, "Melee force of the shieldbug");
|
|
ConVar asw_sb_gallop_min_range("asw_sb_gallop_min_range", "50.0", FCVAR_CHEAT, "Min range to do ram attack");
|
|
ConVar asw_sb_gallop_max_range("asw_sb_gallop_max_range", "130.0", FCVAR_CHEAT, "Max range to do ram attack");
|
|
ConVar asw_old_shieldbug ("asw_old_shieldbug", "0", FCVAR_CHEAT, "1= old shield bug, 0 = new model");
|
|
ConVar asw_shieldbug_force_defend("asw_shieldbug_force_defend", "0", FCVAR_CHEAT, "0 = no force, 1 = force open, 2 = force defend");
|
|
extern ConVar sv_gravity;
|
|
extern ConVar asw_debug_marine_chatter;
|
|
|
|
IMPLEMENT_AUTO_LIST( IShieldbugAutoList );
|
|
|
|
float CASW_Shieldbug::s_fNextSpottedChatterTime = 0;
|
|
|
|
CASW_Shieldbug::CASW_Shieldbug( void )
|
|
{
|
|
m_fMarineBlockCounter = 0;
|
|
m_fLastMarineBlockTime = 0;
|
|
m_flDefendDuration = RandomFloat( 6.0f, 10.0f );
|
|
if ( asw_old_shieldbug.GetBool() )
|
|
{
|
|
m_pszAlienModelName = SWARM_SHIELDBUG_MODEL;
|
|
}
|
|
else
|
|
{
|
|
m_pszAlienModelName = SWARM_NEW_SHIELDBUG_MODEL;
|
|
}
|
|
m_bLastShouldDefend = false;
|
|
m_nDeathStyle = kDIE_FANCY;
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS( asw_shieldbug, CASW_Shieldbug );
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CASW_Shieldbug, DT_ASW_Shieldbug )
|
|
|
|
END_SEND_TABLE()
|
|
|
|
BEGIN_DATADESC( CASW_Shieldbug )
|
|
DEFINE_FIELD(m_bDefending, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_fNextFlinchTime, FIELD_FLOAT),
|
|
DEFINE_FIELD(m_fLastHurtTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_fNextHeadhitAttack, FIELD_TIME),
|
|
DEFINE_KEYFIELD(m_nExtraHeath, FIELD_INTEGER, "extrahealth"),
|
|
END_DATADESC()
|
|
|
|
void CASW_Shieldbug::Spawn( void )
|
|
{
|
|
SetHullType(HULL_WIDE_SHORT);
|
|
|
|
BaseClass::Spawn();
|
|
|
|
m_bHasBeenHurt = false;
|
|
|
|
SetHullType(HULL_WIDE_SHORT);
|
|
|
|
SetViewOffset( Vector(6, 0, 11) ) ; // Position of the eyes relative to NPC's origin.
|
|
|
|
SetHealthByDifficultyLevel();
|
|
m_bDefending = false;
|
|
|
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2); // | bits_CAP_MOVE_JUMP
|
|
CapabilitiesRemove( bits_CAP_MOVE_JUMP );
|
|
|
|
SetCollisionGroup( ASW_COLLISION_GROUP_BIG_ALIEN );
|
|
|
|
m_takedamage = DAMAGE_NO; // alien is invulnerable until she finds her first enemy
|
|
|
|
m_bNeverInstagib = true;
|
|
}
|
|
|
|
CASW_Shieldbug::~CASW_Shieldbug()
|
|
{
|
|
|
|
}
|
|
|
|
void CASW_Shieldbug::Precache( void )
|
|
{
|
|
PrecacheScriptSound("ASW_Drone.Alert");
|
|
PrecacheScriptSound("ASW_Drone.Attack");
|
|
PrecacheScriptSound("ASW_Parasite.Death");
|
|
PrecacheScriptSound("ASW_Parasite.Idle");
|
|
PrecacheScriptSound("ASW_Parasite.Attack");
|
|
|
|
PrecacheScriptSound("ASW_ShieldBug.StepLight");
|
|
PrecacheScriptSound( "ASW_ShieldBug.Pain" );
|
|
PrecacheScriptSound( "ASW_ShieldBug.Alert" );
|
|
PrecacheScriptSound( "ASW_ShieldBug.Death" );
|
|
PrecacheScriptSound( "ASW_ShieldBug.Attack" );
|
|
PrecacheScriptSound( "ASW_ShieldBug.Circle" );
|
|
PrecacheScriptSound( "ASW_ShieldBug.Idle" );
|
|
|
|
// these are all his breakables
|
|
PrecacheModel( "models/aliens/shieldbug/gib_back_leg.mdl");
|
|
PrecacheModel( "models/aliens/shieldbug/gib_leg_claw.mdl");
|
|
PrecacheModel( "models/aliens/shieldbug/gib_leg_middle.mdl");
|
|
PrecacheModel( "models/aliens/shieldbug/gib_leg_upper.mdl");
|
|
PrecacheModel( "models/aliens/shieldbug/gib_leg_l.mdl");
|
|
PrecacheModel( "models/aliens/shieldbug/gib_leg_r.mdl");
|
|
|
|
// particles
|
|
PrecacheParticleSystem ( "shieldbug_brain_explode" );
|
|
PrecacheParticleSystem ( "shieldbug_fountain" );
|
|
PrecacheParticleSystem ( "shieldbug_body_explode" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
float CASW_Shieldbug::GetIdealSpeed() const
|
|
{
|
|
float boost = asw_shieldbug_speedboost.GetFloat();
|
|
if ( m_bDefending )
|
|
{
|
|
boost = 1.0f; //asw_shieldbug_defending_speedboost.GetFloat();
|
|
}
|
|
|
|
return boost * BaseClass::GetIdealSpeed() * m_flPlaybackRate;
|
|
}
|
|
|
|
float CASW_Shieldbug::GetSequenceGroundSpeed( int iSequence )
|
|
{
|
|
float t = SequenceDuration( iSequence );
|
|
|
|
if (t > 0)
|
|
{
|
|
float move_dist = GetSequenceMoveDist( iSequence );
|
|
if (move_dist == 0)
|
|
{
|
|
// return 200;
|
|
/*
|
|
if (iSequence == 2)
|
|
return 150;
|
|
else if (iSequence == 3)
|
|
return 300;
|
|
else if (iSequence == 6)
|
|
return 300;
|
|
else if (iSequence == 7)
|
|
return 300;*/
|
|
}
|
|
return move_dist / t;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
float CASW_Shieldbug::GetIdealAccel( ) const
|
|
{
|
|
return GetIdealSpeed() * 1.5f;
|
|
}
|
|
|
|
float CASW_Shieldbug::MaxYawSpeed( void )
|
|
{
|
|
Activity eActivity = GetActivity();
|
|
|
|
// Stay still while attacking
|
|
if ( eActivity == ACT_MELEE_ATTACK1 )
|
|
return 0.1f;
|
|
|
|
if ( m_bElectroStunned.Get() )
|
|
return 0.1f;
|
|
|
|
switch( eActivity )
|
|
{
|
|
case ACT_TURN_LEFT:
|
|
case ACT_TURN_RIGHT:
|
|
return 20.0;
|
|
break;
|
|
|
|
case ACT_RUN:
|
|
default:
|
|
return 20.0f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CASW_Shieldbug::AlertSound()
|
|
{
|
|
EmitSound("ASW_ShieldBug.Alert");
|
|
}
|
|
|
|
void CASW_Shieldbug::PainSound( const CTakeDamageInfo &info )
|
|
{
|
|
if (gpGlobals->curtime > m_fNextPainSound)
|
|
{
|
|
EmitSound("ASW_ShieldBug.Pain");
|
|
m_fNextPainSound = gpGlobals->curtime + RandomFloat(0.5f, 1.5f);
|
|
}
|
|
}
|
|
|
|
void CASW_Shieldbug::AttackSound()
|
|
{
|
|
EmitSound("ASW_ShieldBug.Attack");
|
|
}
|
|
|
|
void CASW_Shieldbug::IdleSound()
|
|
{
|
|
EmitSound("ASW_ShieldBug.Idle");
|
|
}
|
|
|
|
void CASW_Shieldbug::DeathSound( const CTakeDamageInfo &info )
|
|
{
|
|
EmitSound( "ASW_ShieldBug.Death" );
|
|
}
|
|
|
|
|
|
void CASW_Shieldbug::PrescheduleThink( void )
|
|
{
|
|
BaseClass::PrescheduleThink();
|
|
|
|
if (( m_NPCState == NPC_STATE_COMBAT ) && ( random->RandomFloat( 0, 5 ) < 0.1 ))
|
|
{
|
|
IdleSound();
|
|
}
|
|
}
|
|
|
|
bool CASW_Shieldbug::ShouldDefend()
|
|
{
|
|
if ( asw_shieldbug_force_defend.GetInt() == 1 )
|
|
return false;
|
|
|
|
if ( asw_shieldbug_force_defend.GetInt() == 2 )
|
|
return true;
|
|
|
|
if (!GetEnemy()) // no enemy
|
|
return false;
|
|
|
|
float fEnemyDist = GetAbsOrigin().DistTo(GetEnemy()->GetAbsOrigin());
|
|
if (fEnemyDist > 800.0f) // enemy is too far away
|
|
return false;
|
|
|
|
// if enemy is reasonably far away and we haven't been hurt recently
|
|
if (fEnemyDist > 200.0f && gpGlobals->curtime - m_fLastHurtTime > m_flDefendDuration )
|
|
return false;
|
|
|
|
// randomly leave defending to surprise people if we haven't done a headhit attack in a while
|
|
//if (gpGlobals->curtime > m_fNextHeadhitAttack && random->RandomFloat() > 0.8f)
|
|
//return false;
|
|
|
|
// if we're on full health, don't defend
|
|
if ( !m_bHasBeenHurt )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CASW_Shieldbug::HandleAnimEvent( animevent_t *pEvent )
|
|
{
|
|
int nEvent = pEvent->Event();
|
|
|
|
if ( nEvent == AE_SHIELDBUG_MELEE_HIT1 )
|
|
{
|
|
MeleeAttack( ASW_SHIELDBUG_MELEE1_RANGE, ASWGameRules()->ModifyAlienDamageBySkillLevel(sk_asw_shieldbug_damage.GetFloat()), QAngle( 20.0f, 0.0f, -12.0f ), Vector( -250.0f, 1.0f, 1.0f ) );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_SHIELDBUG_SCREENSHAKE )
|
|
{
|
|
if (asw_shieldbug_screen_shake.GetBool())
|
|
{
|
|
// if we're running forwards, do a screen rumble
|
|
float my = GetPoseParameter("move_yaw");
|
|
float speed = GetAbsVelocity().Length2D();
|
|
if (abs(my) < 30 && speed > 200)
|
|
UTIL_ASW_ScreenShake( GetAbsOrigin(), 4.0, 1.0, 0.5, 1500, SHAKE_START, false );
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_SHIELDBUG_MELEE_HIT2 )
|
|
{
|
|
MeleeAttack( ASW_SHIELDBUG_MELEE1_RANGE, ASWGameRules()->ModifyAlienDamageBySkillLevel(sk_asw_shieldbug_damage.GetFloat()), QAngle( 20.0f, 0.0f, 0.0f ), Vector( -350.0f, 1.0f, 1.0f ) );
|
|
return;
|
|
}
|
|
|
|
// if ( nEvent == AE_SHIELDBUG_MELEE1_SOUND )
|
|
// {
|
|
// EmitSound( "ASW_ShieldBug.Attack" );
|
|
// return;
|
|
// }
|
|
|
|
// if ( nEvent == AE_SHIELDBUG_MELEE2_SOUND )
|
|
// {
|
|
// EmitSound( "ASW_ShieldBug.Attack" );
|
|
// return;
|
|
// }
|
|
|
|
if ( nEvent == AE_SHIELDBUG_START_DEFEND )
|
|
{
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg( "Shieldbug started defending from anim event\n" );
|
|
}
|
|
m_bDefending = true;
|
|
return;
|
|
}
|
|
else if ( nEvent == AE_SHIELDBUG_LEAVE_DEFEND )
|
|
{
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg( "Shieldbug stopped defending from anim event\n" );
|
|
}
|
|
m_bDefending = false;
|
|
return;
|
|
}
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
}
|
|
|
|
//=========================================================
|
|
// GetDeathActivity - determines the best type of death
|
|
// anim to play.
|
|
//=========================================================
|
|
Activity CASW_Shieldbug::GetDeathActivity ( void )
|
|
{
|
|
Activity deathActivity;
|
|
deathActivity = ( Activity ) ACT_DIE_FANCY;
|
|
|
|
if ( m_nDeathStyle == kDIE_FANCY )
|
|
{
|
|
if ( m_bOnFire )
|
|
deathActivity = ( Activity ) ACT_DEATH_FIRE;
|
|
else if ( m_bElectroStunned )
|
|
deathActivity = ( Activity ) ACT_DEATH_ELEC;
|
|
else
|
|
deathActivity = ( Activity ) ACT_DIE_FANCY;
|
|
}
|
|
|
|
BaseClass::GetDeathActivity();
|
|
return deathActivity;
|
|
}
|
|
|
|
|
|
void CASW_Shieldbug::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
BaseClass::Event_Killed( info );
|
|
|
|
ASWFailAdvice()->OnShiedbugKilled();
|
|
}
|
|
|
|
// NOTE: This function doesn't currently get used
|
|
bool CASW_Shieldbug::CorpseGib( const CTakeDamageInfo &info )
|
|
{
|
|
CEffectData data;
|
|
|
|
data.m_vOrigin = WorldSpaceCenter();
|
|
data.m_vNormal = Vector(0,0,1);
|
|
|
|
data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 );
|
|
data.m_flScale = clamp( data.m_flScale, 1, 3 );
|
|
data.m_nColor = 1;
|
|
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg("Dispatching harvester gibs, colour %d\n", data.m_nColor);
|
|
}
|
|
DispatchEffect( "HarvesterGib", data );
|
|
|
|
UTIL_ASW_BloodDrips( GetAbsOrigin(), Vector(0,0,1), BLOOD_COLOR_GREEN, 4 );
|
|
|
|
//EmitSound( "ASW_ShieldBug.Death" );
|
|
|
|
//CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, GetAbsOrigin(), 256, 0.5f, this );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CASW_Shieldbug::BuildScheduleTestBits( void )
|
|
{
|
|
//Don't allow any modifications when scripted
|
|
if ( m_NPCState == NPC_STATE_SCRIPT )
|
|
return;
|
|
|
|
//Make sure we interrupt a run schedule if we can jump
|
|
if ( IsCurSchedule( SCHED_CHASE_ENEMY ) || IsCurSchedule( SCHED_SHOVER_CHASE_ENEMY ) )
|
|
{
|
|
SetCustomInterruptCondition( COND_ENEMY_UNREACHABLE );
|
|
SetCustomInterruptCondition( COND_SHIELDBUG_CHANGE_DEFEND );
|
|
}
|
|
|
|
//Interrupt any schedule unless already fleeing, burrowing, burrowed, or unburrowing.
|
|
if( GetFlags() & FL_ONGROUND )
|
|
{
|
|
if ( GetEnemy() == NULL )
|
|
{
|
|
SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Shieldbug::GatherEnemyConditions( CBaseEntity *pEnemy )
|
|
{
|
|
// Do the base class
|
|
BaseClass::GatherEnemyConditions( pEnemy );
|
|
|
|
// If we're not already too far away, check again
|
|
//TODO: Check to make sure we don't already have a condition set that removes the need for this
|
|
if ( HasCondition( COND_ENEMY_UNREACHABLE ) == false )
|
|
{
|
|
Vector predPosition;
|
|
UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPosition );
|
|
|
|
Vector predDir = ( predPosition - GetAbsOrigin() );
|
|
float predLength = VectorNormalize( predDir );
|
|
|
|
// See if we'll be outside our effective target range
|
|
if ( predLength > 2000 ) // m_flEludeDistance
|
|
{
|
|
Vector predVelDir = ( predPosition - GetEnemy()->GetAbsOrigin() );
|
|
float predSpeed = VectorNormalize( predVelDir );
|
|
|
|
// See if the enemy is moving mostly away from us
|
|
if ( ( predSpeed > 512.0f ) && ( DotProduct( predVelDir, predDir ) > 0.0f ) )
|
|
{
|
|
// Mark the enemy as eluded and burrow away
|
|
ClearEnemyMemory();
|
|
SetEnemy( NULL );
|
|
SetIdealState( NPC_STATE_ALERT );
|
|
SetCondition( COND_ENEMY_UNREACHABLE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Shieldbug::StartTask(const Task_t *pTask)
|
|
{
|
|
switch (pTask->iTask)
|
|
{
|
|
case TASK_MELEE_ATTACK1:
|
|
{
|
|
RemoveAllGestures();
|
|
BaseClass::StartTask(pTask);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
BaseClass::StartTask( pTask );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Shieldbug::RunTask( const Task_t *pTask )
|
|
{
|
|
if (GetActivity() == ACT_RUN) // || GetActivity() == ACT_MELEE_ATTACK2
|
|
{
|
|
if (!m_bDefending)
|
|
m_flPlaybackRate = asw_shieldbug_speedboost.GetFloat();
|
|
else
|
|
m_flPlaybackRate = asw_shieldbug_defending_speedboost.GetFloat();
|
|
}
|
|
|
|
BaseClass::RunTask( pTask );
|
|
}
|
|
|
|
int CASW_Shieldbug::TranslateSchedule( int scheduleType )
|
|
{
|
|
if ( scheduleType == SCHED_MELEE_ATTACK1 )
|
|
{
|
|
//RemoveAllGestures();
|
|
return SCHED_ASW_SHIELDBUG_MELEE_ATTACK1;
|
|
}
|
|
else if ( scheduleType == SCHED_MELEE_ATTACK2 )
|
|
{
|
|
// this var is used to leave attacking mode (so we'll do a charge if we haven't done one for a while)
|
|
m_fNextHeadhitAttack = gpGlobals->curtime + random->RandomFloat(7.0f, 15.0f);
|
|
|
|
return SCHED_ASW_SHIELDBUG_MELEE_ATTACK2;
|
|
}
|
|
|
|
return BaseClass::TranslateSchedule( scheduleType );
|
|
}
|
|
|
|
int CASW_Shieldbug::SelectSchedule()
|
|
{
|
|
if ( HasCondition( COND_NEW_ENEMY ) && GetHealth() > 0 )
|
|
{
|
|
m_takedamage = DAMAGE_YES;
|
|
}
|
|
|
|
if ( HasCondition( COND_SHIELDBUG_CHANGE_DEFEND ) && GetHealth() > 0 )
|
|
{
|
|
if ( m_bLastShouldDefend )
|
|
{
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg( "Doing SCHED_SHIELDBUG_START_DEFENDING\n" );
|
|
}
|
|
return SCHED_SHIELDBUG_START_DEFENDING;
|
|
}
|
|
else
|
|
{
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg( "Doing SCHED_SHIELDBUG_LEAVE_DEFENDING\n" );
|
|
}
|
|
return SCHED_SHIELDBUG_LEAVE_DEFENDING;
|
|
}
|
|
}
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
Activity CASW_Shieldbug::NPC_TranslateActivity( Activity baseAct )
|
|
{
|
|
Activity translated = BaseClass::NPC_TranslateActivity( baseAct );
|
|
|
|
if ( m_bDefending )
|
|
{
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg( "Translating to defend activity\n" );
|
|
}
|
|
if ( translated == ACT_RUN ) return (Activity) ACT_RUN_DEFEND;
|
|
if ( translated == ACT_IDLE ) return (Activity) ACT_IDLE_DEFEND;
|
|
if ( translated == ACT_FLINCH_LEFTARM ) return (Activity) ACT_FLINCH_LEFTARM_DEFEND;
|
|
if ( translated == ACT_FLINCH_RIGHTARM ) return (Activity) ACT_FLINCH_RIGHTARM_DEFEND;
|
|
if ( translated == ACT_MELEE_ATTACK1 ) return (Activity) ACT_MELEE_ATTACK1_DEFEND;
|
|
if ( translated == ACT_TURN_LEFT ) return (Activity) ACT_TURN_LEFT_DEFEND;
|
|
if ( translated == ACT_TURN_RIGHT ) return (Activity) ACT_TURN_RIGHT_DEFEND;
|
|
if ( translated == ACT_ALIEN_SHOVER_ROAR ) return (Activity) ACT_ALIEN_SHOVER_ROAR_DEFEND;
|
|
}
|
|
|
|
return translated;
|
|
}
|
|
|
|
int CASW_Shieldbug::MeleeAttack1Conditions( float flDot, float flDist )
|
|
{
|
|
#if 1 //NOTENOTE: Use predicted position melee attacks
|
|
|
|
float flPrDist, flPrDot;
|
|
Vector vecPrPos;
|
|
Vector2D vec2DPrDir;
|
|
|
|
//Get our likely position in one half second
|
|
UTIL_PredictedPosition( GetEnemy(), 0.5f, &vecPrPos );
|
|
|
|
//Get the predicted distance and direction
|
|
flPrDist = ( vecPrPos - GetAbsOrigin() ).Length();
|
|
vec2DPrDir = ( vecPrPos - GetAbsOrigin() ).AsVector2D();
|
|
|
|
Vector vecBodyDir = BodyDirection2D();
|
|
|
|
Vector2D vec2DBodyDir = vecBodyDir.AsVector2D();
|
|
|
|
flPrDot = DotProduct2D ( vec2DPrDir, vec2DBodyDir );
|
|
|
|
if ( flPrDist > ASW_SHIELDBUG_MELEE1_START_ATTACK_RANGE )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
//if ( flPrDot < 0.5f )
|
|
if ( flPrDot < 0 ) // try generous way
|
|
return COND_NOT_FACING_ATTACK;
|
|
|
|
#else
|
|
|
|
if ( flDot < 0.5f )
|
|
return COND_NOT_FACING_ATTACK;
|
|
|
|
float flAdjustedDist = ASW_SHIELDBUG_MELEE1_START_ATTACK_RANGE;
|
|
|
|
if ( GetEnemy() )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
|
|
|
|
if ( pPlayer && pPlayer->IsInAVehicle() )
|
|
{
|
|
flAdjustedDist *= 2.0f;
|
|
}
|
|
}
|
|
|
|
if ( flDist > flAdjustedDist )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
trace_t tr;
|
|
AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( tr.fraction < 1.0f )
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
return COND_CAN_MELEE_ATTACK1;
|
|
}
|
|
|
|
ConVar asw_shieldbug_knockdown( "asw_shieldbug_knockdown", "1", FCVAR_CHEAT, "If set shieldbug will knock marines down with his melee attacks" );
|
|
ConVar asw_shieldbug_knockdown_force( "asw_shieldbug_knockdown_force", "500", FCVAR_CHEAT, "Magnitude of knockdown force for shieldbug's melee attack" );
|
|
ConVar asw_shieldbug_knockdown_lift( "asw_shieldbug_knockdown_lift", "300", FCVAR_CHEAT, "Upwards force for shieldbug's melee attack" );
|
|
|
|
void CASW_Shieldbug::MeleeAttack( float distance, float damage, QAngle &viewPunch, Vector &shove )
|
|
{
|
|
Vector vecForceDir;
|
|
|
|
// Always hurt bullseyes for now
|
|
if ( ( GetEnemy() != NULL ) && ( GetEnemy()->Classify() == CLASS_BULLSEYE ) )
|
|
{
|
|
vecForceDir = (GetEnemy()->GetAbsOrigin() - GetAbsOrigin());
|
|
CTakeDamageInfo info( this, this, damage, DMG_SLASH );
|
|
CalculateMeleeDamageForce( &info, vecForceDir, GetEnemy()->GetAbsOrigin() );
|
|
GetEnemy()->TakeDamage( info );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pHurt = CheckTraceHullAttack( distance, -Vector(16,16,32), Vector(16,16,32), damage, DMG_SLASH, asw_shieldbug_melee_force.GetFloat() );
|
|
|
|
if ( pHurt && asw_shieldbug_knockdown.GetBool() )
|
|
{
|
|
CASW_Marine *pMarine = CASW_Marine::AsMarine( pHurt );
|
|
if ( pMarine )
|
|
{
|
|
vecForceDir = ( pHurt->WorldSpaceCenter() - WorldSpaceCenter() );
|
|
vecForceDir.NormalizeInPlace();
|
|
vecForceDir *= asw_shieldbug_knockdown_force.GetFloat();
|
|
vecForceDir += Vector( 0, 0, asw_shieldbug_knockdown_lift.GetFloat() );
|
|
pMarine->Knockdown( this, vecForceDir );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CASW_Shieldbug::CheckForShieldbugHint( const CTakeDamageInfo &info )
|
|
{
|
|
if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE)
|
|
{
|
|
// reduce our block counter by the number of seconds that have passed since a marine last shot us in the front
|
|
if (m_fLastMarineBlockTime != 0 && m_fMarineBlockCounter > 0)
|
|
{
|
|
if (asw_debug_marine_chatter.GetBool())
|
|
Msg("Reducing block counter by %f\n", (gpGlobals->curtime - m_fLastMarineBlockTime) * 20.0f);
|
|
m_fMarineBlockCounter -= (gpGlobals->curtime - m_fLastMarineBlockTime) * 20.0f; // counter ticks completely down after 30 seconds
|
|
if (m_fMarineBlockCounter < 0)
|
|
m_fMarineBlockCounter = 0;
|
|
}
|
|
m_fMarineBlockCounter += info.GetDamage();
|
|
if (asw_debug_marine_chatter.GetBool())
|
|
Msg("m_fMarineBlockCounter = %f\n", m_fMarineBlockCounter);
|
|
if (m_fMarineBlockCounter > 600) // 686 = burning a whole rifle clip no normal difficulty
|
|
{
|
|
if (asw_debug_marine_chatter.GetBool())
|
|
Msg("Doing shieldbug hint\n");
|
|
// try to find a nearby marine to shout out a shieldbug hint
|
|
if ( ASWGameResource() )
|
|
{
|
|
CASW_Game_Resource *pGameResource = ASWGameResource();
|
|
// count how many are nearby
|
|
int iNearby = 0;
|
|
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
|
|
{
|
|
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
|
|
CASW_Marine *pMarine = pMR ? pMR->GetMarineEntity() : NULL;
|
|
if (pMarine && (GetAbsOrigin().DistTo(pMarine->GetAbsOrigin()) < 600)
|
|
&& pMarine->GetHealth() > 0)
|
|
iNearby++;
|
|
}
|
|
int iChatter = random->RandomInt(0, iNearby-1);
|
|
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
|
|
{
|
|
CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i);
|
|
CASW_Marine *pMarine = pMR ? pMR->GetMarineEntity() : NULL;
|
|
if (pMarine && (GetAbsOrigin().DistTo(pMarine->GetAbsOrigin()) < 600)
|
|
&& pMarine->GetHealth() > 0)
|
|
{
|
|
if (iChatter <= 0)
|
|
{
|
|
pMarine->GetMarineSpeech()->QueueChatter(CHATTER_SHIELDBUG_HINT, gpGlobals->curtime + 1.0f, gpGlobals->curtime + 3.0f);
|
|
break;
|
|
}
|
|
iChatter--;
|
|
}
|
|
}
|
|
}
|
|
m_fMarineBlockCounter = -1200; // set the block counter way below so we don't shout about shooting from behind again so soon
|
|
}
|
|
m_fLastMarineBlockTime = gpGlobals->curtime;
|
|
}
|
|
}
|
|
|
|
int CASW_Shieldbug::OnTakeDamage_Alive( const CTakeDamageInfo &info )
|
|
{
|
|
m_fLastHurtTime = gpGlobals->curtime;
|
|
|
|
int result = 0;
|
|
|
|
CTakeDamageInfo newInfo(info);
|
|
float damage = info.GetDamage();
|
|
|
|
// reduce damage from shotguns and mining laser
|
|
if (info.GetDamageType() & DMG_ENERGYBEAM)
|
|
{
|
|
damage *= 0.4f;
|
|
}
|
|
if (info.GetDamageType() & DMG_BUCKSHOT)
|
|
{
|
|
// hack to reduce vindicator damage (not reducing normal shotty as much as it's not too strong)
|
|
if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE)
|
|
{
|
|
CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(info.GetAttacker());
|
|
if (pMarine)
|
|
{
|
|
CASW_Weapon_Assault_Shotgun *pVindicator = dynamic_cast<CASW_Weapon_Assault_Shotgun*>(pMarine->GetActiveASWWeapon());
|
|
if ( pVindicator )
|
|
damage *= 0.45f;
|
|
else
|
|
damage *= 0.6f;
|
|
}
|
|
}
|
|
}
|
|
|
|
newInfo.SetDamage( damage );
|
|
|
|
// Get facing direction
|
|
Vector vecFacing;
|
|
QAngle angFacing = GetAbsAngles();
|
|
AngleVectors( angFacing, &vecFacing );
|
|
vecFacing.z = 0;
|
|
VectorNormalize( vecFacing );
|
|
|
|
// Get the dot of the attack
|
|
float fForwardDot = -1.0f;
|
|
|
|
Vector damageNormal;
|
|
Vector vecDamagePos = newInfo.GetDamagePosition();
|
|
if ( newInfo.GetAttacker() && vecDamagePos != vec3_origin )
|
|
{
|
|
vecDamagePos = newInfo.GetAttacker()->GetAbsOrigin(); // use the attacker's position when determining block, to stop marines shooting through gaps and hurting the bug from any angle
|
|
}
|
|
|
|
bool bDirectional = ( vecDamagePos != vec3_origin );
|
|
|
|
if ( bDirectional )
|
|
{
|
|
damageNormal = vecDamagePos - GetAbsOrigin(); // should be head pos
|
|
damageNormal.z = 0;
|
|
|
|
fForwardDot = vecFacing.Dot( damageNormal );
|
|
}
|
|
|
|
if ( m_bDefending && bDirectional && fForwardDot > 0.66f )
|
|
{
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg("Defending, check damage for block\n");
|
|
}
|
|
|
|
// is the attack in front
|
|
if ( ( newInfo.GetDamageType() & DMG_BURN ) != 0 && // extra dot block check is only for flames
|
|
( newInfo.GetDamageType() & DMG_DIRECT ) == 0 ) // burning DoT doesn't get blocked
|
|
{
|
|
m_fLastHurtTime = gpGlobals->curtime;
|
|
CheckForShieldbugHint(newInfo);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
result = BaseClass::OnTakeDamage_Alive(newInfo);
|
|
|
|
if ( result > 0 )
|
|
{
|
|
m_bHasBeenHurt = true;
|
|
|
|
if ( gpGlobals->curtime > m_fNextFlinchTime )
|
|
{
|
|
// was the attacker on the left or the right?
|
|
bool bLeft = true;
|
|
if ( bDirectional && m_hCine == NULL ) // non-locational damage doesn't cause flinch + don't flinch if scripting
|
|
{
|
|
// never flinch if we're being shot from the front
|
|
if ( fForwardDot < 0.66f )
|
|
{
|
|
angFacing = GetAbsAngles();
|
|
angFacing[YAW] += 90;
|
|
AngleVectors(angFacing, &vecFacing);
|
|
vecFacing.z = 0;
|
|
VectorNormalize(vecFacing);
|
|
bLeft = vecFacing.Dot(damageNormal) > 0;
|
|
|
|
if (bLeft)
|
|
SetSchedule(SCHED_SHIELDBUG_FLINCH_LEFT);
|
|
else
|
|
SetSchedule(SCHED_SHIELDBUG_FLINCH_RIGHT);
|
|
|
|
if (asw_debug_shieldbug.GetBool())
|
|
Msg("Flinching! %f forwarddot=%f\n", gpGlobals->curtime, fForwardDot);
|
|
|
|
m_fNextFlinchTime = gpGlobals->curtime + ASW_SHIELDBUG_FLINCH_INTERVAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool CASW_Shieldbug::ShouldGib( const CTakeDamageInfo &info )
|
|
{
|
|
// don't gib if we burnt to death
|
|
if (info.GetDamageType() & DMG_BURN)
|
|
return false;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CASW_Shieldbug::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
|
|
{
|
|
Vector vecFacePosition = vec3_origin;
|
|
CBaseEntity *pFaceTarget = NULL;
|
|
bool bFaceTarget = false;
|
|
|
|
if ( GetEnemy() && GetNavigator()->GetMovementActivity() == ACT_RUN )
|
|
{
|
|
Vector vecEnemyLKP = GetEnemyLKP();
|
|
|
|
// Only start facing when we're close enough
|
|
if ( ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < 512 ) )
|
|
{
|
|
vecFacePosition = vecEnemyLKP;
|
|
pFaceTarget = GetEnemy();
|
|
bFaceTarget = true;
|
|
}
|
|
}
|
|
|
|
// Face
|
|
if ( bFaceTarget )
|
|
{
|
|
AddFacingTarget( pFaceTarget, vecFacePosition, 1.0, 0.2 );
|
|
}
|
|
|
|
return BaseClass::OverrideMoveFacing( move, flInterval );
|
|
}
|
|
|
|
void CASW_Shieldbug::SetTurnActivity ( void )
|
|
{
|
|
if (asw_debug_shieldbug.GetBool())
|
|
Msg("Shieldbug set turn activity\n");
|
|
|
|
//RemoveAllGestures();
|
|
BaseClass::SetTurnActivity();
|
|
|
|
return;
|
|
}
|
|
|
|
// don't use the HL2 method of starting flinches, we only want them to occur when the player is shooting the 'weak spots'
|
|
bool CASW_Shieldbug::CanFlinch( void )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void CASW_Shieldbug::SetHealthByDifficultyLevel()
|
|
{
|
|
SetHealth( ASWGameRules()->ModifyAlienHealthBySkillLevel( 1000 ) + m_nExtraHeath ); // was 500 - 2/19/10
|
|
}
|
|
|
|
void CASW_Shieldbug::ASW_Ignite( float flFlameLifetime, float flSize, CBaseEntity *pAttacker, CBaseEntity *pDamagingWeapon /*= NULL */ )
|
|
{
|
|
BaseClass::ASW_Ignite(MIN(flFlameLifetime, 3.0f), flSize, pAttacker, pDamagingWeapon );
|
|
}
|
|
|
|
void CASW_Shieldbug::NPCThink()
|
|
{
|
|
BaseClass::NPCThink();
|
|
if ( GetEfficiency() < AIE_DORMANT && GetSleepState() == AISS_AWAKE
|
|
&& gpGlobals->curtime > s_fNextSpottedChatterTime && GetEnemy())
|
|
{
|
|
CASW_Marine *pMarine = UTIL_ASW_Marine_Can_Chatter_Spot(this);
|
|
if (pMarine)
|
|
{
|
|
pMarine->GetMarineSpeech()->Chatter(CHATTER_SHIELDBUG);
|
|
s_fNextSpottedChatterTime = gpGlobals->curtime + 30.0f;
|
|
}
|
|
else
|
|
s_fNextSpottedChatterTime = gpGlobals->curtime + 1.0f;
|
|
}
|
|
}
|
|
|
|
// is the enemy near enough to left slash at?
|
|
int CASW_Shieldbug::MeleeAttack2Conditions ( float flDot, float flDist )
|
|
{
|
|
if ( flDist > asw_sb_gallop_max_range.GetFloat())
|
|
{
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
}
|
|
else if (flDist < asw_sb_gallop_min_range.GetFloat())
|
|
{
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
}
|
|
|
|
if (m_bDefending)
|
|
return false;
|
|
|
|
return COND_CAN_MELEE_ATTACK2;
|
|
}
|
|
|
|
int CASW_Shieldbug::GetMoneyCount( const CTakeDamageInfo &info )
|
|
{
|
|
return RandomInt( 3, 5 );
|
|
}
|
|
|
|
void CASW_Shieldbug::GatherConditions()
|
|
{
|
|
BaseClass::GatherConditions();
|
|
|
|
m_bLastShouldDefend = ShouldDefend();
|
|
if ( m_bDefending != m_bLastShouldDefend )
|
|
{
|
|
if ( asw_debug_shieldbug.GetBool() )
|
|
{
|
|
Msg( "Setting COND_SHIELDBUG_CHANGE_DEFEND\n" );
|
|
}
|
|
SetCondition( COND_SHIELDBUG_CHANGE_DEFEND );
|
|
}
|
|
else
|
|
{
|
|
ClearCondition( COND_SHIELDBUG_CHANGE_DEFEND );
|
|
}
|
|
}
|
|
|
|
int CASW_Shieldbug::DrawDebugTextOverlays()
|
|
{
|
|
int text_offset = BaseClass::DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr( "Defending = %d", m_bDefending ),0);
|
|
text_offset++;
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr( "ShouldDefend = %d", ShouldDefend() ),0);
|
|
text_offset++;
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr( "COND_SHIELDBUG_CHANGE_DEFEND = %d", HasCondition( COND_SHIELDBUG_CHANGE_DEFEND ) ),0);
|
|
text_offset++;
|
|
}
|
|
return text_offset;
|
|
}
|
|
|
|
AI_BEGIN_CUSTOM_NPC( asw_shieldbug, CASW_Shieldbug )
|
|
// Activities
|
|
DECLARE_ACTIVITY( ACT_SHIELDBUG_PRE_LEAP )
|
|
DECLARE_ACTIVITY( ACT_START_DEFENDING );
|
|
DECLARE_ACTIVITY( ACT_LEAVE_DEFENDING );
|
|
DECLARE_ACTIVITY( ACT_RUN_DEFEND );
|
|
DECLARE_ACTIVITY( ACT_IDLE_DEFEND );
|
|
DECLARE_ACTIVITY( ACT_FLINCH_LEFTARM_DEFEND );;
|
|
DECLARE_ACTIVITY( ACT_FLINCH_RIGHTARM_DEFEND );
|
|
DECLARE_ACTIVITY( ACT_MELEE_ATTACK1_DEFEND );
|
|
DECLARE_ACTIVITY( ACT_TURN_LEFT_DEFEND );
|
|
DECLARE_ACTIVITY( ACT_TURN_RIGHT_DEFEND );
|
|
DECLARE_ACTIVITY( ACT_ALIEN_SHOVER_ROAR_DEFEND );
|
|
|
|
// Events
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_WALK_FOOTSTEP )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_FOOTSTEP_SOFT )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_FOOTSTEP_HEAVY )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_MELEE_HIT1 )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_MELEE_HIT2 )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_MELEE1_SOUND )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_MELEE2_SOUND )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_MOUTH_BLEED )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_ALERT_SOUND )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_SCREENSHAKE )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_START_DEFEND )
|
|
DECLARE_ANIMEVENT( AE_SHIELDBUG_LEAVE_DEFEND )
|
|
|
|
DECLARE_CONDITION( COND_SHIELDBUG_CHANGE_DEFEND )
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SHIELDBUG_FLINCH_LEFT,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_FLINCH_LEFTARM"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SHIELDBUG_FLINCH_RIGHT,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_FLINCH_RIGHTARM"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SHIELDBUG_START_DEFENDING,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_START_DEFENDING"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SHIELDBUG_LEAVE_DEFENDING,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_LEAVE_DEFENDING"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_SHIELDBUG_MELEE_ATTACK1,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0" // asw comment test
|
|
// " TASK_STOP_MOVING 1" // asw 1 = just clear goal
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 0"
|
|
""
|
|
" Interrupts"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_SHIELDBUG_MELEE_ATTACK2,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0" // asw comment test
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
//" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SHIELDBUG_PRE_LEAP"
|
|
" TASK_MELEE_ATTACK2 0"
|
|
""
|
|
" Interrupts"
|
|
)
|
|
AI_END_CUSTOM_NPC()
|
|
|
|
|