2024-08-29 19:18:30 -04:00
# include "cbase.h"
# include "asw_queen.h"
# include "asw_queen_spit.h"
# include "te_effect_dispatch.h"
# include "npc_bullseye.h"
# include "npcevent.h"
# include "asw_marine.h"
# include "asw_marine_resource.h"
# include "asw_parasite.h"
# include "asw_buzzer.h"
# include "asw_game_resource.h"
# include "asw_gamerules.h"
# include "soundenvelope.h"
# include "ai_memory.h"
# include "ai_moveprobe.h"
# include "asw_util_shared.h"
# include "asw_queen_divers_shared.h"
# include "asw_queen_grabber_shared.h"
# include "asw_colonist.h"
# include "ndebugoverlay.h"
# include "asw_weapon_assault_shotgun_shared.h"
# include "asw_sentry_base.h"
# include "props.h"
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
# define SWARM_QUEEN_MODEL "models / swarm / Queen / Queen.mdl"
//#define SWARM_QUEEN_MODEL "models/antlion_guard.mdl"
# define ASW_QUEEN_MAX_ATTACK_DISTANCE 1500
// define this to make the queen not move/turn
//#define ASW_QUEEN_STATIONARY
LINK_ENTITY_TO_CLASS ( asw_queen , CASW_Queen ) ;
IMPLEMENT_SERVERCLASS_ST ( CASW_Queen , DT_ASW_Queen )
SendPropExclude ( " DT_BaseAnimating " , " m_flPoseParameter " ) ,
SendPropEHandle ( SENDINFO ( m_hQueenEnemy ) ) ,
SendPropBool ( SENDINFO ( m_bChestOpen ) ) ,
SendPropInt ( SENDINFO ( m_iMaxHealth ) , 14 , SPROP_UNSIGNED ) ,
END_SEND_TABLE ( )
BEGIN_DATADESC ( CASW_Queen )
DEFINE_FIELD ( m_angQueenFacing , FIELD_VECTOR ) ,
DEFINE_FIELD ( m_vecLastClawPos , FIELD_VECTOR ) ,
DEFINE_FIELD ( m_bChestOpen , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_fLastHeadYaw , FIELD_FLOAT ) ,
DEFINE_FIELD ( m_fLastShieldPose , FIELD_FLOAT ) ,
DEFINE_FIELD ( m_iSpitNum , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_iDiverState , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_fLastDiverAttack , FIELD_TIME ) ,
DEFINE_FIELD ( m_fNextDiverState , FIELD_TIME ) ,
DEFINE_FIELD ( m_hPreventMovementMarine , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hGrabbingEnemy , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hPrimaryGrabber , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hRetreatSpot , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_fLastRangedAttack , FIELD_FLOAT ) ,
DEFINE_FIELD ( m_iCrittersAlive , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_hBlockingSentry , FIELD_EHANDLE ) ,
DEFINE_OUTPUT ( m_OnSummonWave1 , " OnSummonWave1 " ) ,
DEFINE_OUTPUT ( m_OnSummonWave2 , " OnSummonWave2 " ) ,
DEFINE_OUTPUT ( m_OnSummonWave3 , " OnSummonWave3 " ) ,
DEFINE_OUTPUT ( m_OnSummonWave4 , " OnSummonWave4 " ) ,
DEFINE_OUTPUT ( m_OnQueenKilled , " OnQueenKilled " ) ,
END_DATADESC ( )
// Activities
int ACT_QUEEN_SCREAM ;
int ACT_QUEEN_SCREAM_LOW ;
int ACT_QUEEN_SINGLE_SPIT ;
int ACT_QUEEN_TRIPLE_SPIT ;
int AE_QUEEN_SPIT ;
int AE_QUEEN_START_SPIT ;
int ACT_QUEEN_LOW_IDLE ;
int ACT_QUEEN_LOW_TO_HIGH ;
int ACT_QUEEN_HIGH_TO_LOW ;
int ACT_QUEEN_TENTACLE_ATTACK ;
// AnimEvents
int AE_QUEEN_SLASH_HIT ;
int AE_QUEEN_R_SLASH_HIT ;
int AE_QUEEN_START_SLASH ;
ConVar asw_queen_health_easy ( " asw_queen_health_easy " , " 2500 " , FCVAR_CHEAT , " Initial health of the Swarm Queen " ) ;
ConVar asw_queen_health_normal ( " asw_queen_health_normal " , " 3500 " , FCVAR_CHEAT , " Initial health of the Swarm Queen " ) ;
ConVar asw_queen_health_hard ( " asw_queen_health_hard " , " 5000 " , FCVAR_CHEAT , " Initial health of the Swarm Queen " ) ;
ConVar asw_queen_health_insane ( " asw_queen_health_insane " , " 6000 " , FCVAR_CHEAT , " Initial health of the Swarm Queen " ) ;
ConVar asw_queen_slash_damage ( " asw_queen_slash_damage " , " 5 " , FCVAR_CHEAT , " Damage caused by the Swarm Queen's slash attack " ) ;
ConVar asw_queen_slash_size ( " asw_queen_slash_size " , " 100 " , FCVAR_CHEAT , " Padding around the Swarm Queen's claw when calculating melee attack collision " ) ;
ConVar asw_queen_slash_debug ( " asw_queen_slash_debug " , " 0 " , FCVAR_CHEAT , " Visualize Swarm Queen slash collision " ) ;
ConVar asw_queen_slash_range ( " asw_queen_slash_range " , " 200 " , FCVAR_CHEAT , " Range of Swarm Queen slash attack " ) ;
ConVar asw_queen_min_mslash ( " asw_queen_min_mslash " , " 160 " , FCVAR_CHEAT , " Min Range of Swarm Queen moving slash attack " ) ;
ConVar asw_queen_max_mslash ( " asw_queen_max_mslash " , " 350 " , FCVAR_CHEAT , " Max Range of Swarm Queen moving slash attack " ) ;
ConVar asw_queen_spit_autoaim_angle ( " asw_queen_spit_autoaim_angle " , " 10 " , FCVAR_CHEAT , " Angle in degrees in which the Queen's spit attack will adjust to fire at a marine " ) ;
ConVar asw_queen_debug ( " asw_queen_debug " , " 0 " , FCVAR_CHEAT , " Display debug info about the queen " ) ;
ConVar asw_queen_flame_flinch_chance ( " asw_queen_flame_flinch_chance " , " 0 " , FCVAR_CHEAT , " Chance of queen flinching when she takes fire damage " ) ;
ConVar asw_queen_force_parasite_spawn ( " asw_queen_force_parasite_spawn " , " 0 " , FCVAR_CHEAT , " Set to 1 to force the queen to spawn parasites " ) ;
ConVar asw_queen_force_spit ( " asw_queen_force_spit " , " 0 " , FCVAR_CHEAT , " Set to 1 to force the queen to spit " ) ;
# define ASW_QUEEN_CLAW_MINS Vector(-asw_queen_slash_size.GetFloat(), -asw_queen_slash_size.GetFloat(), -asw_queen_slash_size.GetFloat() * 2.0f)
# define ASW_QUEEN_CLAW_MAXS Vector(asw_queen_slash_size.GetFloat(), asw_queen_slash_size.GetFloat(), asw_queen_slash_size.GetFloat() * 2.0f)
# define ASW_QUEEN_SLASH_DAMAGE asw_queen_slash_damage.GetInt()
# define ASW_QUEEN_MELEE_RANGE asw_queen_slash_range.GetFloat()
# define ASW_QUEEN_MELEE2_MIN_RANGE asw_queen_min_mslash.GetFloat()
# define ASW_QUEEN_MELEE2_MAX_RANGE asw_queen_max_mslash.GetFloat()
// health points at which the queen will stop to call in waves of allies
# define QUEEN_SUMMON_WAVE_POINT_1 0.8f // wave of drones
# define QUEEN_SUMMON_WAVE_POINT_2 0.6f // wave of buzzers
# define QUEEN_SUMMON_WAVE_POINT_3 0.4f // wave of drone jumpers
# define QUEEN_SUMMON_WAVE_POINT_4 0.2f // wave of shieldbugs
# define ASW_DIVER_ATTACK_CHANCE 0.5f
# define ASW_DIVER_ATTACK_INTERVAL 20.0f
# define ASW_RANGED_ATTACK_INTERVAL 30.0f
# define ASW_MAX_QUEEN_PARASITES 5
CASW_Queen : : CASW_Queen ( )
{
m_iDiverState = ASW_QUEEN_DIVER_NONE ;
m_fLastDiverAttack = 0 ;
m_iCrittersAlive = 0 ;
m_fLayParasiteTime = 0 ;
m_iCrittersSpawnedRecently = 0 ;
m_pszAlienModelName = SWARM_QUEEN_MODEL ;
m_nAlienCollisionGroup = ASW_COLLISION_GROUP_ALIEN ;
}
CASW_Queen : : ~ CASW_Queen ( )
{
}
void CASW_Queen : : Spawn ( void )
{
SetHullType ( HULL_LARGE_CENTERED ) ;
BaseClass : : Spawn ( ) ;
SetHullType ( HULL_LARGE_CENTERED ) ;
//UTIL_SetSize(this, Vector(-23,-23,0), Vector(23,23,69));
//UTIL_SetSize(this, Vector(-140, -140, 0), Vector(140, 140, 200) );
# ifdef ASW_QUEEN_STATIONARY
UTIL_SetSize ( this , Vector ( - 140 , - 40 , 0 ) , Vector ( 140 , 40 , 200 ) ) ;
# else
UTIL_SetSize ( this , Vector ( - 120 , - 120 , 0 ) , Vector ( 120 , 120 , 160 ) ) ;
# endif
SetHealthByDifficultyLevel ( ) ;
CapabilitiesAdd ( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 //| bits_CAP_INNATE_RANGE_ATTACK2
| bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 ) ;
# ifdef ASW_QUEEN_STATIONARY
CapabilitiesRemove ( bits_CAP_MOVE_GROUND ) ;
# endif
m_flDistTooFar = 9999999.0f ;
m_angQueenFacing = GetAbsAngles ( ) ;
m_hDiver = CASW_Queen_Divers : : Create_Queen_Divers ( this ) ;
//CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
m_takedamage = DAMAGE_NO ; // queen is invulnerable until she finds her first enemy
m_hRetreatSpot = gEntList . FindEntityByClassname ( NULL , " asw_queen_retreat_spot " ) ;
}
void CASW_Queen : : NPCInit ( )
{
BaseClass : : NPCInit ( ) ;
//SetDistSwarmSense(1024.0f);
//SetDistLook(1024.0f);
}
void CASW_Queen : : Precache ( void )
{
PrecacheScriptSound ( " ASW_Queen.Death " ) ;
PrecacheScriptSound ( " ASW_Queen.Pain " ) ;
PrecacheScriptSound ( " ASW_Queen.PainBig " ) ;
PrecacheScriptSound ( " ASW_Queen.Slash " ) ;
PrecacheScriptSound ( " ASW_Queen.SlashShort " ) ;
PrecacheScriptSound ( " ASW_Queen.AttackWave " ) ;
PrecacheScriptSound ( " ASW_Queen.Spit " ) ;
PrecacheScriptSound ( " ASW_Queen.TentacleAttackStart " ) ;
BaseClass : : Precache ( ) ;
}
// queen doesn't move, like Kompressor does not dance
float CASW_Queen : : GetIdealSpeed ( ) const
{
# ifdef ASW_QUEEN_STATIONARY
return 0 ;
# else
return BaseClass : : GetIdealSpeed ( ) * m_flPlaybackRate ;
# endif
}
float CASW_Queen : : GetIdealAccel ( ) const
{
return GetIdealSpeed ( ) * 1.5f ;
}
// queen doesn't turn
float CASW_Queen : : MaxYawSpeed ( void )
{
# ifdef ASW_QUEEN_STATIONARY
return 0 ;
# else
Activity eActivity = GetActivity ( ) ;
//CBaseEntity *pEnemy = GetEnemy();
// Stay still
if ( ( eActivity = = ACT_MELEE_ATTACK1 ) )
return 0.0f ;
return 20 ;
# endif
}
// ============================= SOUNDS =============================
void CASW_Queen : : AlertSound ( )
{
// no alert sound atm
//EmitSound("ASW_ShieldBug.Alert");
}
void CASW_Queen : : PainSound ( const CTakeDamageInfo & info )
{
if ( gpGlobals - > curtime > m_fNextPainSound )
{
if ( info . GetInflictor ( ) = = m_hDiver . Get ( ) ) // if the damage comes from our vulnerable divers, then scream big time
EmitSound ( " ASW_Queen.PainBig " ) ;
else
EmitSound ( " ASW_Queen.Pain " ) ;
m_fNextPainSound = gpGlobals - > curtime + 0.5f ;
}
//SetChestOpen(!m_bChestOpen);
}
void CASW_Queen : : AttackSound ( )
{
if ( IsCurSchedule ( SCHED_MELEE_ATTACK2 ) )
EmitSound ( " ASW_Queen.SlashShort " ) ;
else
EmitSound ( " ASW_Queen.Slash " ) ;
}
void CASW_Queen : : SummonWaveSound ( )
{
EmitSound ( " ASW_Queen.AttackWave " ) ;
}
void CASW_Queen : : IdleSound ( )
{
// queen has no idle...
//EmitSound("ASW_ShieldBug.Idle");
}
void CASW_Queen : : DeathSound ( const CTakeDamageInfo & info )
{
EmitSound ( " ASW_Queen.Death " ) ;
}
// ============================= END SOUNDS =============================
// make the queen always look in his starting direction
bool CASW_Queen : : OverrideMoveFacing ( const AILocalMoveGoal_t & move , float flInterval )
{
# ifdef ASW_QUEEN_STATIONARY
Vector vForward ;
AngleVectors ( m_angQueenFacing , & vForward ) ;
AddFacingTarget ( vForward , 1.0f , 0.2f ) ;
# endif
return BaseClass : : OverrideMoveFacing ( move , flInterval ) ;
}
void CASW_Queen : : HandleAnimEvent ( animevent_t * pEvent )
{
int nEvent = pEvent - > Event ( ) ;
if ( nEvent = = AE_QUEEN_SLASH_HIT )
{
SlashAttack ( false ) ;
return ;
}
else if ( nEvent = = AE_QUEEN_R_SLASH_HIT )
{
SlashAttack ( true ) ;
return ;
}
else if ( nEvent = = AE_QUEEN_START_SLASH )
{
AttackSound ( ) ;
m_vecLastClawPos = vec3_origin ;
return ;
}
else if ( nEvent = = AE_QUEEN_START_SPIT )
{
m_iSpitNum = 0 ;
return ;
}
else if ( nEvent = = AE_QUEEN_SPIT )
{
SpitProjectile ( ) ;
m_iSpitNum + + ;
return ;
}
BaseClass : : HandleAnimEvent ( pEvent ) ;
}
// queen can attack without LOS so long as they're near enough
bool CASW_Queen : : WeaponLOSCondition ( const Vector & ownerPos , const Vector & targetPos , bool bSetConditions )
{
if ( targetPos . DistTo ( ownerPos ) < ASW_QUEEN_MAX_ATTACK_DISTANCE )
return true ;
return false ;
}
// is the enemy near enough to left slash at?
int CASW_Queen : : MeleeAttack1Conditions ( float flDot , float flDist )
{
float fRangeBoost = 1.0f ;
if ( flDot > 0 )
{
fRangeBoost = 1.0f + ( 1.0f - flDot ) * 0.25f ; // 25% range boost at fldot of 0
}
if ( flDist > ASW_QUEEN_MELEE_RANGE * fRangeBoost )
{
return COND_TOO_FAR_TO_ATTACK ;
}
/*else if (GetEnemy() == NULL)
{
return 0 ;
}
// check he's to our left
Vector diff = GetEnemy ( ) - > GetAbsOrigin ( ) - GetAbsOrigin ( ) ;
float yaw = UTIL_VecToYaw ( diff ) ;
yaw = AngleDiff ( yaw , GetAbsAngles ( ) [ YAW ] ) ;
if ( yaw < 0 )
return 0 ; */
return COND_CAN_MELEE_ATTACK1 ;
}
// attack 2 is the moving attack, enemy has to be over x units away
int CASW_Queen : : MeleeAttack2Conditions ( float flDot , float flDist )
{
float fRangeBoost = 1.0f ;
if ( flDot > 0 )
{
fRangeBoost = 1.0f + ( 1.0f - flDot ) * 0.25f ; // 25% range boost at fldot of 0
}
if ( flDist > ASW_QUEEN_MELEE2_MAX_RANGE * fRangeBoost )
{
return COND_TOO_FAR_TO_ATTACK ;
}
if ( flDist < ASW_QUEEN_MELEE2_MIN_RANGE )
{
return COND_TOO_CLOSE_TO_ATTACK ;
}
/*else if (GetEnemy() == NULL)
{
return 0 ;
}
// check he's to our right
Vector diff = GetEnemy ( ) - > GetAbsOrigin ( ) - GetAbsOrigin ( ) ;
float yaw = UTIL_VecToYaw ( diff ) ;
yaw = AngleDiff ( yaw , GetAbsAngles ( ) [ YAW ] ) ;
if ( yaw > 0 )
return 0 ;
*/
return COND_CAN_MELEE_ATTACK2 ;
}
//-----------------------------------------------------------------------------
// Purpose: For innate melee attack
// Input :
// Output :
//-----------------------------------------------------------------------------
float CASW_Queen : : InnateRange1MinRange ( void )
{
return ASW_QUEEN_MELEE_RANGE ;
}
float CASW_Queen : : InnateRange1MaxRange ( void )
{
return ASW_QUEEN_MAX_ATTACK_DISTANCE ;
}
int CASW_Queen : : RangeAttack1Conditions ( float flDot , float flDist )
{
if ( flDist < ASW_QUEEN_MELEE_RANGE )
{
return COND_TOO_CLOSE_TO_ATTACK ;
}
else if ( flDist > ASW_QUEEN_MAX_ATTACK_DISTANCE )
{
return COND_TOO_FAR_TO_ATTACK ;
}
else if ( flDot < 0.5 )
{
return COND_NOT_FACING_ATTACK ;
}
// we also have a timer that can prevent us from attacking, make sure we don't try while we're still in that time
if ( gpGlobals - > curtime < = m_fLastRangedAttack + ASW_RANGED_ATTACK_INTERVAL )
return COND_TOO_FAR_TO_ATTACK ;
return COND_CAN_RANGE_ATTACK1 ;
}
bool CASW_Queen : : FCanCheckAttacks ( )
{
if ( GetNavType ( ) = = NAV_CLIMB | | GetNavType ( ) = = NAV_JUMP )
return false ;
//if ( HasCondition(COND_SEE_ENEMY) && !HasCondition( COND_ENEMY_TOO_FAR))
//{
return true ;
//}
//return false;
}
void CASW_Queen : : GatherConditions ( )
{
BaseClass : : GatherConditions ( ) ;
ClearCondition ( COND_QUEEN_BLOCKED_BY_DOOR ) ;
if ( m_hBlockingSentry . Get ( ) )
{
SetCondition ( COND_QUEEN_BLOCKED_BY_DOOR ) ;
}
}
int CASW_Queen : : SelectSchedule ( )
{
if ( HasCondition ( COND_NEW_ENEMY ) & & GetHealth ( ) > 0 )
{
m_takedamage = DAMAGE_YES ;
}
if ( HasCondition ( COND_FLOATING_OFF_GROUND ) )
{
SetGravity ( 1.0 ) ;
SetGroundEntity ( NULL ) ;
return SCHED_FALL_TO_GROUND ;
}
if ( m_NPCState = = NPC_STATE_COMBAT )
return SelectQueenCombatSchedule ( ) ;
return BaseClass : : SelectSchedule ( ) ;
}
int CASW_Queen : : SelectQueenCombatSchedule ( )
{
if ( asw_queen_force_spit . GetBool ( ) )
{
asw_queen_force_spit . SetValue ( false ) ;
return SCHED_RANGE_ATTACK1 ;
}
if ( asw_queen_force_parasite_spawn . GetBool ( ) )
{
asw_queen_force_parasite_spawn . SetValue ( false ) ;
return SCHED_ASW_SPAWN_PARASITES ;
}
// see if we were hurt, if so, flinch!
int nSched = SelectFlinchSchedule_ASW ( ) ;
if ( nSched ! = SCHED_NONE )
return nSched ;
// if we're in the middle of a diver attack, just wait
if ( m_iDiverState > ASW_QUEEN_DIVER_IDLE )
{
return SCHED_WAIT_DIVER ;
}
// wake up angrily when we first see a marine
if ( HasCondition ( COND_NEW_ENEMY ) & & gpGlobals - > curtime - GetEnemies ( ) - > FirstTimeSeen ( GetEnemy ( ) ) < 2.0 )
{
return SCHED_WAKE_ANGRY ;
}
// if our enemy died, clear him and try to find another
if ( HasCondition ( COND_ENEMY_DEAD ) )
{
SetEnemy ( NULL ) ;
if ( ChooseEnemy ( ) )
{
ClearCondition ( COND_ENEMY_DEAD ) ;
return SelectSchedule ( ) ;
}
SetState ( NPC_STATE_ALERT ) ;
return SelectSchedule ( ) ;
}
if ( GetShotRegulator ( ) - > IsInRestInterval ( ) )
{
if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
return SCHED_COMBAT_FACE ;
}
// see if it's time to call in a wave of allies
nSched = SelectSummonSchedule ( ) ;
if ( nSched ! = SCHED_NONE )
{
return nSched ;
}
// occasionally do a diver attack
if ( m_fLastDiverAttack = = 0 )
m_fLastDiverAttack = gpGlobals - > curtime ; // forces delay before first diver attack
if ( ( m_fLastDiverAttack = = 0 | | gpGlobals - > curtime > m_fLastDiverAttack + ASW_DIVER_ATTACK_INTERVAL )
)
//&& random->RandomFloat() > ASW_DIVER_ATTACK_CHANCE)
return SCHED_START_DIVER_ATTACK ;
// if we're blocked by a sentry, smash that mofo
if ( HasCondition ( COND_QUEEN_BLOCKED_BY_DOOR ) )
return SCHED_SMASH_SENTRY ;
// melee if we can
if ( HasCondition ( COND_CAN_MELEE_ATTACK1 ) )
return SCHED_MELEE_ATTACK1 ;
if ( HasCondition ( COND_CAN_MELEE_ATTACK2 ) )
return SCHED_MELEE_ATTACK2 ;
# ifdef ASW_QUEEN_STATIONARY
// we can see the enemy
if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
{
return SCHED_RANGE_ATTACK1 ;
}
if ( HasCondition ( COND_CAN_RANGE_ATTACK2 ) )
return SCHED_RANGE_ATTACK2 ;
# else
if ( m_fLastRangedAttack = = 0 )
m_fLastRangedAttack = gpGlobals - > curtime ; // forces delay before first diver attack
if ( gpGlobals - > curtime > m_fLastRangedAttack + ASW_RANGED_ATTACK_INTERVAL )
{
if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
{
m_fLastRangedAttack = gpGlobals - > curtime ;
// randomly either spit or spawn parasites for our ranged attack
//if (m_iCrittersAlive < ASW_MAX_QUEEN_PARASITES && random->RandomFloat() < 0.5f)
//return SCHED_ASW_SPAWN_PARASITES;
return SCHED_RANGE_ATTACK1 ;
}
/*if ( HasCondition(COND_CAN_RANGE_ATTACK2) )
{
m_fLastRangedAttack = gpGlobals - > curtime ;
return SCHED_RANGE_ATTACK2 ;
} */
}
# endif
if ( HasCondition ( COND_NOT_FACING_ATTACK ) )
return SCHED_COMBAT_FACE ;
// if we're not attacking, then just look at them
# ifdef ASW_QUEEN_STATIONARY
return SCHED_COMBAT_FACE ;
# else
return SCHED_CHASE_ENEMY ;
# endif
DevWarning ( 2 , " No suitable combat schedule! \n " ) ;
return SCHED_FAIL ;
}
int CASW_Queen : : TranslateSchedule ( int scheduleType )
{
if ( scheduleType = = SCHED_RANGE_ATTACK1 )
{
RemoveAllGestures ( ) ;
return SCHED_QUEEN_RANGE_ATTACK ;
}
else if ( scheduleType = = SCHED_ASW_ALIEN_MELEE_ATTACK1 | | scheduleType = = SCHED_MELEE_ATTACK2 )
{
RemoveAllGestures ( ) ;
}
return BaseClass : : TranslateSchedule ( scheduleType ) ;
}
bool CASW_Queen : : ShouldGib ( const CTakeDamageInfo & info )
{
return false ;
}
void CASW_Queen : : SetChestOpen ( bool bOpen )
{
if ( bOpen ! = m_bChestOpen )
{
m_bChestOpen = bOpen ;
}
}
int CASW_Queen : : SelectDeadSchedule ( )
{
// Adrian - Alread dead (by animation event maybe?)
// Is it safe to set it to SCHED_NONE?
if ( m_lifeState = = LIFE_DEAD )
return SCHED_NONE ;
CleanupOnDeath ( ) ;
return SCHED_DIE ;
}
void CASW_Queen : : UpdatePoseParams ( )
{
/*
float yaw = m_fLastHeadYaw ; //GetPoseParameter( LookupPoseParameter("head_yaw") );
if ( m_hQueenEnemy . Get ( ) ! = NULL )
{
Vector enemyDir = m_hQueenEnemy - > WorldSpaceCenter ( ) - WorldSpaceCenter ( ) ;
VectorNormalize ( enemyDir ) ;
float angle = VecToYaw ( BodyDirection3D ( ) ) ;
float angleDiff = VecToYaw ( enemyDir ) ;
angleDiff = UTIL_AngleDiff ( angleDiff , angle + yaw ) ;
//ASW_ClampYaw(500.0f, yaw, yaw + angleDiff, gpGlobals->frametime);
//Msg("yaw=%f targ=%f delta=%f ", yaw, yaw+angleDiff, gpGlobals->frametime * 3.0f);
yaw = ASW_Linear_Approach ( yaw , yaw + angleDiff , gpGlobals - > frametime * 1200.0f ) ;
//Msg(" result=%f\n", yaw);
SetPoseParameter ( " head_yaw " , yaw ) ;
m_fLastHeadYaw = yaw ;
//SetPoseParameter( "head_yaw", Approach( yaw + angleDiff, yaw, 5 ) );
}
else
{
// Otherwise turn the head back to its normal position
//ASW_ClampYaw(500.0f, yaw, 0, gpGlobals->frametime);
yaw = ASW_Linear_Approach ( yaw , 0 , gpGlobals - > frametime * 1200.0f ) ;
SetPoseParameter ( " head_yaw " , yaw ) ;
m_fLastHeadYaw = yaw ;
//SetPoseParameter( "head_yaw", Approach( 0, yaw, 10 ) );
}
*/
float shield = m_fLastShieldPose ; //GetPoseParameter( LookupPoseParameter("shield_open") );
float targetshield = m_bChestOpen ? 1.0f : 0.0f ;
if ( shield ! = targetshield )
{
shield = ASW_Linear_Approach ( shield , targetshield , gpGlobals - > frametime * 3.0f ) ;
m_fLastShieldPose = shield ;
}
SetPoseParameter ( " shield_open " , shield ) ;
}
bool CASW_Queen : : ShouldWatchEnemy ( )
{
/*Activity nActivity = GetActivity();
if ( ( nActivity = = ACT_ANTLIONGUARD_SEARCH ) | |
( nActivity = = ACT_ANTLIONGUARD_PEEK_ENTER ) | |
( nActivity = = ACT_ANTLIONGUARD_PEEK_EXIT ) | |
( nActivity = = ACT_ANTLIONGUARD_PEEK1 ) | |
( nActivity = = ACT_ANTLIONGUARD_PEEK_SIGHTED ) | |
( nActivity = = ACT_ANTLIONGUARD_SHOVE_PHYSOBJECT ) | |
( nActivity = = ACT_ANTLIONGUARD_PHYSHIT_FR ) | |
( nActivity = = ACT_ANTLIONGUARD_PHYSHIT_FL ) | |
( nActivity = = ACT_ANTLIONGUARD_PHYSHIT_RR ) | |
( nActivity = = ACT_ANTLIONGUARD_PHYSHIT_RL ) | |
( nActivity = = ACT_ANTLIONGUARD_CHARGE_CRASH ) | |
( nActivity = = ACT_ANTLIONGUARD_CHARGE_HIT ) | |
( nActivity = = ACT_ANTLIONGUARD_CHARGE_ANTICIPATION ) )
{
return false ;
} */
return true ;
}
void CASW_Queen : : PrescheduleThink ( )
{
BaseClass : : PrescheduleThink ( ) ;
// Don't do anything after death
if ( m_NPCState = = NPC_STATE_DEAD )
return ;
m_hQueenEnemy = GetEnemy ( ) ;
UpdatePoseParams ( ) ;
UpdateDiver ( ) ;
//Msg("%f: UpdatePoseParams\n", gpGlobals->curtime);
}
int CASW_Queen : : SelectFlinchSchedule_ASW ( )
{
// don't flinch if we didn't take any heavy damage
if ( ! HasCondition ( COND_HEAVY_DAMAGE ) ) // && !HasCondition(COND_LIGHT_DAMAGE)
return SCHED_NONE ;
// don't flinch midway through a flinch
if ( IsCurSchedule ( SCHED_BIG_FLINCH ) )
return SCHED_NONE ;
// only flinch if shot during a melee attack
//if (! (GetTask() && (GetTask()->iTask == TASK_MELEE_ATTACK1)) )
//return SCHED_NONE;
// Do the flinch, if we have the anim
Activity iFlinchActivity = GetFlinchActivity ( true , false ) ;
if ( HaveSequenceForActivity ( iFlinchActivity ) )
return SCHED_BIG_FLINCH ;
return SCHED_NONE ;
}
// see if we've been hurt enough to warrant summoning a wave
int CASW_Queen : : SelectSummonSchedule ( )
{
switch ( m_iSummonWave )
{
case 0 : if ( GetHealth ( ) < = QUEEN_SUMMON_WAVE_POINT_1 * GetMaxHealth ( ) ) return SCHED_ASW_RETREAT_AND_SUMMON ; break ;
case 1 : if ( GetHealth ( ) < = QUEEN_SUMMON_WAVE_POINT_2 * GetMaxHealth ( ) ) return SCHED_ASW_RETREAT_AND_SUMMON ; break ;
case 2 : if ( GetHealth ( ) < = QUEEN_SUMMON_WAVE_POINT_3 * GetMaxHealth ( ) ) return SCHED_ASW_RETREAT_AND_SUMMON ; break ;
case 3 : if ( GetHealth ( ) < = QUEEN_SUMMON_WAVE_POINT_4 * GetMaxHealth ( ) ) return SCHED_ASW_RETREAT_AND_SUMMON ; break ;
default : return SCHED_NONE ; break ;
}
return SCHED_NONE ;
}
void CASW_Queen : : StartTask ( const Task_t * pTask )
{
switch ( pTask - > iTask )
{
# ifdef ASW_QUEEN_STATIONARY
case TASK_FACE_IDEAL :
case TASK_FACE_ENEMY :
{
// stationary queen doesn't turn
TaskComplete ( ) ;
break ;
}
# endif
case TASK_CLEAR_BLOCKING_SENTRY :
{
m_hBlockingSentry = NULL ;
TaskComplete ( ) ;
break ;
}
case TASK_FACE_SENTRY :
{
if ( ! m_hBlockingSentry . Get ( ) )
{
TaskFail ( " No sentry to smash \n " ) ;
}
else
{
Vector vecEnemyLKP = m_hBlockingSentry - > GetAbsOrigin ( ) ;
if ( ! FInAimCone ( vecEnemyLKP ) )
{
GetMotor ( ) - > SetIdealYawToTarget ( vecEnemyLKP ) ;
GetMotor ( ) - > SetIdealYaw ( CalcReasonableFacing ( true ) ) ; // CalcReasonableFacing() is based on previously set ideal yaw
SetTurnActivity ( ) ;
}
else
{
float flReasonableFacing = CalcReasonableFacing ( true ) ;
if ( fabsf ( flReasonableFacing - GetMotor ( ) - > GetIdealYaw ( ) ) < 1 )
TaskComplete ( ) ;
else
{
GetMotor ( ) - > SetIdealYaw ( flReasonableFacing ) ;
SetTurnActivity ( ) ;
}
}
}
break ;
break ;
}
case TASK_ASW_SOUND_SUMMON :
{
SummonWaveSound ( ) ;
TaskComplete ( ) ;
break ;
}
case TASK_ASW_SUMMON_WAVE :
{
// fire our outputs to summon waves
switch ( m_iSummonWave )
{
case 0 : m_OnSummonWave1 . FireOutput ( this , this ) ; break ;
case 1 : m_OnSummonWave2 . FireOutput ( this , this ) ; break ;
case 2 : m_OnSummonWave3 . FireOutput ( this , this ) ; break ;
case 3 : m_OnSummonWave4 . FireOutput ( this , this ) ; break ;
default : break ;
}
m_iSummonWave + + ;
TaskComplete ( ) ;
break ;
}
case TASK_ASW_WAIT_DIVER :
{
// make sure we're still doing this activity (can be broken out of it by a grenade flinch)
SetIdealActivity ( ( Activity ) ACT_QUEEN_TENTACLE_ATTACK ) ;
break ;
}
case TASK_ASW_START_DIVER_ATTACK :
{
if ( m_iDiverState > ASW_QUEEN_DIVER_IDLE )
{
// already diver attacking
TaskComplete ( ) ;
}
m_fLastDiverAttack = gpGlobals - > curtime ;
// set us plunging, which will set off the whole diver attacking routine and wait schedules
SetDiverState ( ASW_QUEEN_DIVER_PLUNGING ) ;
RemoveAllGestures ( ) ;
// make us play an anim while we plunge those divers into the ground
SetIdealActivity ( ( Activity ) ACT_QUEEN_TENTACLE_ATTACK ) ;
TaskComplete ( ) ;
break ;
}
case TASK_ASW_GET_PATH_TO_RETREAT_SPOT :
{
if ( m_hRetreatSpot = = NULL )
{
TaskFail ( " Tried to find a path to NULL retreat spot! \n " ) ;
break ;
}
Vector vecGoalPos = m_hRetreatSpot - > GetAbsOrigin ( ) ;
AI_NavGoal_t goal ( GOALTYPE_LOCATION , vecGoalPos , ACT_RUN ) ;
if ( GetNavigator ( ) - > SetGoal ( goal ) )
{
if ( asw_queen_debug . GetInt ( ) = = 1 )
{
NDebugOverlay : : Cross3D ( vecGoalPos , Vector ( 8 , 8 , 8 ) , - Vector ( 8 , 8 , 8 ) , 0 , 255 , 0 , true , 2.0f ) ;
NDebugOverlay : : Line ( vecGoalPos , m_hRetreatSpot - > WorldSpaceCenter ( ) , 0 , 255 , 0 , true , 2.0f ) ;
}
// Face the enemy
GetNavigator ( ) - > SetArrivalDirection ( m_hRetreatSpot - > GetAbsAngles ( ) ) ;
TaskComplete ( ) ;
}
else
{
if ( asw_queen_debug . GetInt ( ) = = 1 )
{
NDebugOverlay : : Cross3D ( vecGoalPos , Vector ( 8 , 8 , 8 ) , - Vector ( 8 , 8 , 8 ) , 255 , 0 , 0 , true , 2.0f ) ;
NDebugOverlay : : Line ( vecGoalPos , m_hRetreatSpot - > WorldSpaceCenter ( ) , 255 , 0 , 0 , true , 2.0f ) ;
}
TaskFail ( " Unable to navigate to retreat spot attack target! \n " ) ;
break ;
}
}
break ;
case TASK_SPAWN_PARASITES :
{
RemoveAllGestures ( ) ;
SetChestOpen ( true ) ;
// make us play an anim while we spawn parasites
SetIdealActivity ( ( Activity ) ACT_QUEEN_TENTACLE_ATTACK ) ;
m_fLayParasiteTime = gpGlobals - > curtime + 1.0f ;
m_iCrittersSpawnedRecently = 0 ;
}
break ;
default :
BaseClass : : StartTask ( pTask ) ;
break ;
}
}
void CASW_Queen : : RunTask ( const Task_t * pTask )
{
switch ( pTask - > iTask )
{
case TASK_ASW_SUMMON_WAVE :
{
// should never get into here
break ;
}
case TASK_FACE_SENTRY :
{
// If the yaw is locked, this function will not act correctly
Assert ( GetMotor ( ) - > IsYawLocked ( ) = = false ) ;
if ( ! m_hBlockingSentry . Get ( ) )
{
TaskFail ( " No sentry! \n " ) ;
}
else
{
Vector vecEnemyLKP = m_hBlockingSentry - > GetAbsOrigin ( ) ;
if ( ! FInAimCone ( vecEnemyLKP ) )
{
GetMotor ( ) - > SetIdealYawToTarget ( vecEnemyLKP ) ;
GetMotor ( ) - > SetIdealYaw ( CalcReasonableFacing ( true ) ) ; // CalcReasonableFacing() is based on previously set ideal yaw
}
else
{
float flReasonableFacing = CalcReasonableFacing ( true ) ;
if ( fabsf ( flReasonableFacing - GetMotor ( ) - > GetIdealYaw ( ) ) > 1 )
GetMotor ( ) - > SetIdealYaw ( flReasonableFacing ) ;
}
GetMotor ( ) - > UpdateYaw ( ) ;
if ( FacingIdeal ( ) )
{
TaskComplete ( ) ;
}
}
break ;
}
case TASK_ASW_WAIT_DIVER :
{
if ( m_iDiverState < = ASW_QUEEN_DIVER_IDLE )
TaskComplete ( ) ;
break ;
}
case TASK_SPAWN_PARASITES :
{
if ( gpGlobals - > curtime > m_fLayParasiteTime )
{
if ( m_iCrittersAlive > = ASW_MAX_QUEEN_PARASITES | | m_iCrittersSpawnedRecently > ASW_MAX_QUEEN_PARASITES )
{
SetChestOpen ( false ) ;
TaskComplete ( ) ;
}
else
{
SpawnParasite ( ) ;
m_fLayParasiteTime = gpGlobals - > curtime + 1.5f ; // setup timer to spawn another one until we're at our max
}
}
break ;
}
default :
{
BaseClass : : RunTask ( pTask ) ;
break ;
}
}
}
void CASW_Queen : : SlashAttack ( bool bRightClaw )
{
//Msg("Queen slash attack\n");
Vector vecClawBase ;
Vector vecClawTip ;
QAngle angClaw ;
if ( bRightClaw )
{
if ( ! GetAttachment ( " RightClawBase " , vecClawBase , angClaw ) )
{
Msg ( " Error, failed to find Queen claw attachment point \n " ) ;
return ;
}
if ( ! GetAttachment ( " RightClawPoint " , vecClawTip , angClaw ) )
{
Msg ( " Error, failed to find Queen claw attachment point \n " ) ;
return ;
}
}
else
{
if ( ! GetAttachment ( " LeftClawBase " , vecClawBase , angClaw ) )
{
Msg ( " Error, failed to find Queen claw attachment point \n " ) ;
return ;
}
if ( ! GetAttachment ( " LeftClawPoint " , vecClawTip , angClaw ) )
{
Msg ( " Error, failed to find Queen claw attachment point \n " ) ;
return ;
}
}
if ( asw_queen_slash_debug . GetBool ( ) )
Msg ( " Slash trace: cycle = %f \n " , GetCycle ( ) ) ;
// find the midpoint of the claw, then move it back towards the queen origin a bit (this makes her swipes occur as a volume inside her reach, still hitting marines up close)
Vector vecMidPoint = ( vecClawBase + vecClawTip ) * 0.5f ;
Vector diff = vecMidPoint - GetAbsOrigin ( ) ;
vecMidPoint - = diff * 0.3f ;
// if we don't have a last claw pos, this must be the starting point of a sweep
// store current claw pos so we can do the sweeping hull check from here next time
if ( m_vecLastClawPos = = vec3_origin )
{
m_vecLastClawPos = vecMidPoint ;
return ;
}
// sweep an expanded collision test hull from the tip of the claw to the base
trace_t tr ;
Ray_t ray ;
CASW_TraceFilterOnlyQueenTargets filter ( this , COLLISION_GROUP_NONE ) ;
ray . Init ( m_vecLastClawPos , vecMidPoint , ASW_QUEEN_CLAW_MINS , ASW_QUEEN_CLAW_MAXS ) ;
enginetrace - > TraceRay ( ray , MASK_SOLID , & filter , & tr ) ;
if ( tr . m_pEnt )
{
CBaseEntity * pEntity = tr . m_pEnt ;
CTakeDamageInfo info ( this , this , ASWGameRules ( ) - > ModifyAlienDamageBySkillLevel ( ASW_QUEEN_SLASH_DAMAGE ) , DMG_SLASH ) ;
info . SetDamagePosition ( vecMidPoint ) ;
Vector force = vecMidPoint - m_vecLastClawPos ;
force . NormalizeInPlace ( ) ;
if ( force . IsZero ( ) )
info . SetDamageForce ( Vector ( 0.1 , 0.1 , 0.1 ) ) ;
else
info . SetDamageForce ( force * 10000 ) ;
CASW_Alien * pAlien = dynamic_cast < CASW_Alien * > ( pEntity ) ;
if ( pAlien )
pAlien - > MeleeBleed ( & info ) ;
CASW_Marine * pMarine = CASW_Marine : : AsMarine ( pEntity ) ;
if ( pMarine )
pMarine - > MeleeBleed ( & info ) ;
else
{
CASW_Colonist * pColonist = dynamic_cast < CASW_Colonist * > ( pEntity ) ;
if ( pColonist )
{
pColonist - > MeleeBleed ( & info ) ;
}
else
{
CASW_Sentry_Base * pSentry = dynamic_cast < CASW_Sentry_Base * > ( pEntity ) ;
if ( pSentry )
{
// scale the damage up a bit so we don't take so many swipes to kill the sentry
info . ScaleDamage ( 5.55f ) ;
Vector position = pSentry - > GetAbsOrigin ( ) + Vector ( 0 , 0 , 30 ) ;
Vector sparkNormal = GetAbsOrigin ( ) - position ;
sparkNormal . z = 0 ;
sparkNormal . NormalizeInPlace ( ) ;
CPVSFilter filter ( position ) ;
filter . SetIgnorePredictionCull ( true ) ;
te - > Sparks ( filter , 0.0 , & position , 1 , 1 , & sparkNormal ) ;
}
}
}
// change damage type to make sure we burst explosive barrels
if ( dynamic_cast < CBreakableProp * > ( pEntity ) )
info . SetDamageType ( DMG_BULLET ) ;
pEntity - > TakeDamage ( info ) ;
if ( asw_queen_slash_debug . GetBool ( ) )
{
Msg ( " Slash hit %d %s \n " , tr . m_pEnt - > entindex ( ) , tr . m_pEnt - > GetClassname ( ) ) ;
NDebugOverlay : : SweptBox ( m_vecLastClawPos , vecMidPoint , ASW_QUEEN_CLAW_MINS , ASW_QUEEN_CLAW_MAXS , vec3_angle , 255 , 255 , 0 , 0 , 1.0f ) ;
NDebugOverlay : : Line ( vecMidPoint , tr . m_pEnt - > GetAbsOrigin ( ) , 255 , 255 , 0 , false , 1.0f ) ;
}
}
else
{
if ( asw_queen_slash_debug . GetBool ( ) )
{
NDebugOverlay : : SweptBox ( m_vecLastClawPos , vecMidPoint , ASW_QUEEN_CLAW_MINS , ASW_QUEEN_CLAW_MAXS , vec3_angle , 255 , 0 , 0 , 0 , 1.0f ) ;
}
}
m_vecLastClawPos = vecMidPoint ;
}
void CASW_Queen : : SpitProjectile ( )
{
// Get angle from our head bone attachment (or do it by m_iSpitNum?)
Vector vecSpitSource ;
QAngle angSpit ;
if ( ! GetAttachment ( " SpitSource " , vecSpitSource , angSpit ) )
{
Msg ( " Error, failed to find Queen spit attachment point \n " ) ;
return ;
}
//Msg("SpitSource pos = %s ", VecToString(vecSpitSource));
//Msg("ang = %s (", VecToString(angSpit));
Vector vecAiming ;
AngleVectors ( angSpit , & vecAiming ) ;
//Msg("%s)\n", VecToString(vecAiming));
// angle it flat
angSpit [ PITCH ] = 0 ;
// do an autoaim routine in that rough direction to see if we can angle the shot to hit a marine
Vector vecThrow = GetQueenAutoaimVector ( vecSpitSource , angSpit ) ;
//Msg(" autoaim changed to %s\n", VecToString(vecThrow));
// setup the speed
VectorScale ( vecThrow , 1000.0f , vecThrow ) ;
// create it!
CASW_Queen_Spit : : Queen_Spit_Create ( vecSpitSource , angSpit , vecThrow , AngularImpulse ( 0 , 0 , 0 ) , this ) ;
// problems: shot comes from up high, meaning it should be very easy to dodge
// could make it an AoE explosion so if they don't dodge enough, they'll get caught in the splash damage
// make a sound for the spit
EmitSound ( " ASW_Queen.Spit " ) ;
}
// don't hurt ourselves ever
float CASW_Queen : : GetAttackDamageScale ( CBaseEntity * pVictim )
{
if ( pVictim = = this )
return 0 ;
return BaseClass : : GetAttackDamageScale ( pVictim ) ;
}
Vector CASW_Queen : : GetQueenAutoaimVector ( Vector & spitSrc , QAngle & angSpit )
{
Vector vecResult ;
// find a marine close to this vector
CASW_Game_Resource * pGameResource = ASWGameResource ( ) ;
if ( ! pGameResource )
return Vector ( 0 , 0 , 0 ) ;
CASW_Marine * pChosenMarine = NULL ;
float fClosestAngle = 999 ;
float fChosenYaw = 0 ;
Vector vecChosenDiff ;
for ( int i = 0 ; i < pGameResource - > GetMaxMarineResources ( ) ; i + + )
{
CASW_Marine_Resource * pMarineResource = pGameResource - > GetMarineResource ( i ) ;
if ( ! pMarineResource )
continue ;
CASW_Marine * pMarine = pMarineResource - > GetMarineEntity ( ) ;
if ( ! pMarine )
continue ;
Vector diff = pMarine - > GetAbsOrigin ( ) - spitSrc ;
if ( diff . Length2D ( ) > ASW_QUEEN_MAX_ATTACK_DISTANCE * 1.5f )
continue ;
// todo: do movement prediction
float fYawToMarine = UTIL_VecToYaw ( diff ) ;
float fYawDiff = AngleDiff ( fYawToMarine , angSpit [ YAW ] ) ;
if ( abs ( fYawDiff ) < abs ( fClosestAngle ) )
{
fClosestAngle = fYawDiff ;
pChosenMarine = pMarine ;
fChosenYaw = fYawToMarine ;
vecChosenDiff = diff ;
}
}
if ( pChosenMarine )
{
// we have a marine to autoaim at
if ( abs ( fClosestAngle ) < asw_queen_spit_autoaim_angle . GetFloat ( ) )
{
// adjust the yaw to point directly at him
angSpit [ YAW ] = fChosenYaw ;
// adjust the pitch so it lands near him
angSpit [ PITCH ] = UTIL_VecToPitch ( vecChosenDiff ) ;
// convert to a vector
AngleVectors ( angSpit , & vecResult ) ;
return vecResult ;
}
}
AngleVectors ( angSpit , & vecResult ) ;
return vecResult ;
}
// todo: should depend on how far away the enemy is?
# define ASW_DIVER_CHASE_TIME 6.0f
void CASW_Queen : : SetDiverState ( int iNewState )
{
if ( ! m_hDiver . Get ( ) )
return ;
m_iDiverState = iNewState ;
if ( iNewState > = ASW_QUEEN_DIVER_PLUNGING & & iNewState < = ASW_QUEEN_DIVER_RETRACTING )
{
m_hDiver . Get ( ) - > SetBurrowing ( true ) ;
}
else
{
m_hDiver . Get ( ) - > SetBurrowing ( false ) ;
}
if ( iNewState > = ASW_QUEEN_DIVER_PLUNGING & & iNewState < = ASW_QUEEN_DIVER_UNPLUNGING )
{
SetChestOpen ( true ) ;
m_fLastDiverAttack = gpGlobals - > curtime ;
}
else
{
SetChestOpen ( false ) ;
}
if ( iNewState = = ASW_QUEEN_DIVER_CHASING )
{
if ( m_iLiveGrabbers > 0 )
{
Msg ( " WARNING: Queen started chasing when already had some live grabbers " ) ;
}
// we've started a chase! init the grabber pos and set it on its merry way
Vector vecGrabberPos = GetDiverSpot ( ) ;
//vecGrabberPos.z += 30;
CASW_Queen_Grabber * pGrabber = CASW_Queen_Grabber : : Create_Queen_Grabber ( this , vecGrabberPos , GetAbsAngles ( ) ) ;
if ( pGrabber )
{
m_iLiveGrabbers = 1 ;
pGrabber - > MakePrimary ( ) ;
pGrabber - > m_fMaxChasingTime = gpGlobals - > curtime + 6.0f ; // 6 seconds of chasing
m_hPrimaryGrabber = pGrabber ;
}
}
else if ( iNewState = = ASW_QUEEN_DIVER_GRABBING )
{
m_hGrabbingEnemy = GetEnemy ( ) ;
}
if ( iNewState < = ASW_QUEEN_DIVER_IDLE )
m_hDiver . Get ( ) - > SetVisible ( false ) ;
// set time for next diver state
switch ( iNewState )
{
case ASW_QUEEN_DIVER_PLUNGING : m_fNextDiverState = gpGlobals - > curtime + 1.0f ; break ;
case ASW_QUEEN_DIVER_CHASING : m_fNextDiverState = 0 ; break ; // Grabber will advance us to the grabbing state when he catches us
case ASW_QUEEN_DIVER_GRABBING : m_fNextDiverState = 0 ; break ; // Grabber will advance us to the retracting state when all grabbers are shot away
case ASW_QUEEN_DIVER_RETRACTING : m_fNextDiverState = 0 ; break ; // Primary grabber will advance us to unplunging when he's back home
case ASW_QUEEN_DIVER_UNPLUNGING : m_fNextDiverState = gpGlobals - > curtime + 1.5f ; break ;
default : m_fNextDiverState = 0 ; break ;
}
//Msg("Diver state set to %d\n", iNewState);
}
void CASW_Queen : : NotifyGrabberKilled ( CASW_Queen_Grabber * pGrabber )
{
if ( pGrabber = = m_hPrimaryGrabber . Get ( ) )
m_hPrimaryGrabber = NULL ;
m_iLiveGrabbers - - ;
if ( m_iLiveGrabbers < 0 )
{
m_iLiveGrabbers = 0 ;
Msg ( " WARNING: Live grabbers went below 0 \n " ) ;
}
if ( m_iLiveGrabbers < = 0 )
{
// all grabbers are dead
SetDiverState ( ASW_QUEEN_DIVER_RETRACTING ) ;
if ( m_hPreventMovementMarine . Get ( ) )
m_hPreventMovementMarine - > m_bPreventMovement = false ;
}
}
void CASW_Queen : : AdvanceDiverState ( )
{
if ( m_iDiverState > = ASW_QUEEN_DIVER_UNPLUNGING )
SetDiverState ( ASW_QUEEN_DIVER_IDLE ) ;
else
SetDiverState ( m_iDiverState + 1 ) ;
}
void CASW_Queen : : UpdateDiver ( )
{
if ( m_fNextDiverState ! = 0 )
{
if ( gpGlobals - > curtime > = m_fNextDiverState )
{
AdvanceDiverState ( ) ;
}
}
if ( m_iDiverState = = ASW_QUEEN_DIVER_CHASING )
{
// todo: pick the grabber target enemy when we start chasing and don't change midchase
if ( ! GetEnemy ( ) | | ! m_hDiver . Get ( ) )
{
// todo: stop chasing and retract? or change enemy?
}
//CASW_Queen_Divers *pDiver = m_hDiver.Get();
// we're chasing down our enemy! duhh duh duh duh duuhh duh duh duh OH NOES!
}
}
int CASW_Queen : : OnTakeDamage_Alive ( const CTakeDamageInfo & info )
{
CTakeDamageInfo newInfo ( info ) ;
float damage = info . GetDamage ( ) ;
// reduce all damage because the queen is TUFF!
damage * = 0.2f ;
// 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 ;
}
}
}
// make queen immune to buzzers
if ( dynamic_cast < CASW_Buzzer * > ( info . GetAttacker ( ) ) )
{
return 0 ;
}
// make queen immune to crush damage
if ( info . GetDamageType ( ) & DMG_CRUSH )
{
return 0 ;
}
newInfo . SetDamage ( damage ) ;
return BaseClass : : OnTakeDamage_Alive ( newInfo ) ;
}
void CASW_Queen : : Event_Killed ( const CTakeDamageInfo & info )
{
BaseClass : : Event_Killed ( info ) ;
m_OnQueenKilled . FireOutput ( this , this ) ;
// make sure to kill our grabbers when queen dies
if ( m_hPrimaryGrabber . Get ( ) )
{
CTakeDamageInfo info2 ( info ) ;
info2 . SetDamage ( 1000 ) ;
m_hPrimaryGrabber - > OnTakeDamage ( info2 ) ;
}
CEffectData data ;
data . m_nEntIndex = entindex ( ) ;
CPASFilter filter ( GetAbsOrigin ( ) ) ;
filter . SetIgnorePredictionCull ( true ) ;
data . m_vOrigin = GetAbsOrigin ( ) ;
DispatchEffect ( filter , 0.0 , " QueenDie " , data ) ;
// if we're in the middle of plunging divers into the ground, stop it (for other states, killing our grabber will trigger the whole retract sequence of events)
if ( m_iDiverState = = ASW_QUEEN_DIVER_PLUNGING )
{
SetDiverState ( ASW_QUEEN_DIVER_NONE ) ;
if ( m_hDiver . Get ( ) )
m_hDiver . Get ( ) - > SetBurrowing ( false ) ;
}
}
Vector CASW_Queen : : GetDiverSpot ( )
{
Vector vecForward ;
AngleVectors ( GetAbsAngles ( ) , & vecForward ) ;
vecForward . z = 0 ;
return GetAbsOrigin ( ) + vecForward * 80 ;
}
// don't allow us to be hurt by allies
bool CASW_Queen : : PassesDamageFilter ( const CTakeDamageInfo & info )
{
if ( info . GetAttacker ( ) & & IsAlienClass ( info . GetAttacker ( ) - > Classify ( ) ) )
return false ;
return BaseClass : : PassesDamageFilter ( info ) ;
}
void CASW_Queen : : SetHealthByDifficultyLevel ( )
{
int health = 5000 ;
if ( ASWGameRules ( ) )
{
switch ( ASWGameRules ( ) - > GetSkillLevel ( ) )
{
case 1 : health = asw_queen_health_easy . GetInt ( ) ; break ;
case 2 : health = asw_queen_health_normal . GetInt ( ) ; break ;
case 3 : health = asw_queen_health_hard . GetInt ( ) ; break ;
case 4 : health = asw_queen_health_insane . GetInt ( ) ; break ;
2024-08-29 20:12:35 -04:00
case 5 : health = asw_queen_health_insane . GetInt ( ) ; break ;
2024-08-29 19:18:30 -04:00
default : 5000 ;
}
}
SetHealth ( health ) ;
SetMaxHealth ( health ) ;
}
int CASW_Queen : : DrawDebugTextOverlays ( )
{
int text_offset = BaseClass : : DrawDebugTextOverlays ( ) ;
if ( m_debugOverlays & OVERLAY_TEXT_BIT )
{
NDebugOverlay : : EntityText ( entindex ( ) , text_offset , m_bChestOpen ? " Chest Open " : " Chest Closed " , 0 ) ;
text_offset + + ;
char buffer [ 256 ] ;
Q_snprintf ( buffer , sizeof ( buffer ) , " shieldpos %f \n " , m_fLastShieldPose ) ;
NDebugOverlay : : EntityText ( entindex ( ) , text_offset , buffer , 0 ) ;
text_offset + + ;
NDebugOverlay : : EntityText ( entindex ( ) , text_offset , HasCondition ( COND_CAN_RANGE_ATTACK1 ) ? " Can Range Attack " : " No Range attack " , 0 ) ;
text_offset + + ;
NDebugOverlay : : EntityText ( entindex ( ) , text_offset , HasCondition ( COND_CAN_MELEE_ATTACK1 ) ? " Can Melee Attack1 " : " No Melee Attack1 " , 0 ) ;
text_offset + + ;
NDebugOverlay : : EntityText ( entindex ( ) , text_offset , HasCondition ( COND_CAN_MELEE_ATTACK2 ) ? " Can Melee Attack2 " : " No Melee aAttack2 " , 0 ) ;
text_offset + + ;
}
return text_offset ;
}
void CASW_Queen : : DrawDebugGeometryOverlays ( )
{
// draw arrows showing the extent of our melee attacks
for ( int i = 0 ; i < 360 ; i + = 45 )
{
float flBaseSize = 10 ;
Vector vBasePos = GetAbsOrigin ( ) + Vector ( 0 , 0 , 5 ) ;
QAngle angles ( 0 , 0 , 0 ) ;
Vector vForward , vRight , vUp ;
float flHeight = ASW_QUEEN_MELEE2_MAX_RANGE ;
angles [ YAW ] = i ;
AngleVectors ( angles , & vForward , & vRight , & vUp ) ;
NDebugOverlay : : Triangle ( vBasePos + vRight * flBaseSize / 2 , vBasePos - vRight * flBaseSize / 2 , vBasePos + vForward * flHeight , 128 , 0 , 0 , 128 , false , 0.1 ) ;
flHeight = ASW_QUEEN_MELEE2_MIN_RANGE ;
angles [ YAW ] = i + 5 ;
AngleVectors ( angles , & vForward , & vRight , & vUp ) ;
vBasePos . z + = 1 ;
NDebugOverlay : : Triangle ( vBasePos + vRight * flBaseSize / 2 , vBasePos - vRight * flBaseSize / 2 , vBasePos + vForward * flHeight , 255 , 0 , 0 , 128 , false , 0.1 ) ;
flHeight = ASW_QUEEN_MELEE_RANGE ;
angles [ YAW ] = i + 10 ;
AngleVectors ( angles , & vForward , & vRight , & vUp ) ;
vBasePos . z + = 2 ;
NDebugOverlay : : Triangle ( vBasePos + vRight * flBaseSize / 2 , vBasePos - vRight * flBaseSize / 2 , vBasePos + vForward * flHeight , 0 , 255 , 0 , 128 , false , 0.1 ) ;
}
BaseClass : : DrawDebugGeometryOverlays ( ) ;
}
// the queen often flinches on explosions and fire damage
bool CASW_Queen : : IsHeavyDamage ( const CTakeDamageInfo & info )
{
// shock damage never causes flinching
if ( ( info . GetDamageType ( ) & DMG_SHOCK ) ! = 0 )
return false ;
// explosions always cause a flinch
if ( ( info . GetDamageType ( ) & DMG_BLAST ) ! = 0 )
return true ;
// flame causes a flinch some of the time
if ( ( info . GetDamageType ( ) & DMG_BURN ) ! = 0 )
{
float f = random - > RandomFloat ( ) ;
bool bFlinch = ( f < asw_queen_flame_flinch_chance . GetFloat ( ) ) ;
if ( bFlinch )
Msg ( " Queen flinching from fire \n " ) ;
return bFlinch ;
}
return false ;
}
void CASW_Queen : : BuildScheduleTestBits ( void )
{
// Ignore damage if we were recently damaged or we're attacking.
if ( GetActivity ( ) = = ACT_MELEE_ATTACK1 | | GetActivity ( ) = = ACT_MELEE_ATTACK2 )
{
ClearCustomInterruptCondition ( COND_LIGHT_DAMAGE ) ;
}
BaseClass : : BuildScheduleTestBits ( ) ;
}
CAI_BaseNPC * CASW_Queen : : SpawnParasite ( )
{
CBaseEntity * pEntity = CreateEntityByName ( " asw_parasite " ) ;
CAI_BaseNPC * pNPC = dynamic_cast < CAI_BaseNPC * > ( pEntity ) ;
if ( ! pNPC )
{
Warning ( " NULL Ent in CASW_Queen::SpawnParasite \n " ) ;
return NULL ;
}
// spawn slightly in front of us and up, to be where the chest is
Vector vecSpawnOffset = Vector ( 70 , 0 , 0 ) ; // was 10, if we're attempting the jump
Vector vecSpawnPos ( 0 , 0 , 0 ) ;
matrix3x4_t matrix ;
QAngle angFacing = GetAbsAngles ( ) ;
AngleMatrix ( angFacing , matrix ) ;
VectorTransform ( vecSpawnOffset , matrix , vecSpawnPos ) ;
vecSpawnPos + = GetAbsOrigin ( ) ;
//if (m_iCrittersAlive == 0)
//{
//NDebugOverlay::Axis(vecSpawnPos, angFacing, 10, false, 20.0f);
//NDebugOverlay::Axis(GetAbsOrigin(), angFacing, 10, false, 20.0f);
//}
pNPC - > AddSpawnFlags ( SF_NPC_FALL_TO_GROUND ) ; // stops it teleporting to the ground on spawn
pNPC - > SetAbsOrigin ( vecSpawnPos ) ;
// Strip pitch and roll from the spawner's angles. Pass only yaw to the spawned NPC.
QAngle angles = GetAbsAngles ( ) ;
angles . x = 0.0 ;
angles . z = 0.0 ;
// vary the yaw a bit
angles . y + = random - > RandomFloat ( - 30 , 30 ) ;
pNPC - > SetAbsAngles ( angles ) ;
IASW_Spawnable_NPC * pSpawnable = dynamic_cast < IASW_Spawnable_NPC * > ( pNPC ) ;
ASSERT ( pSpawnable ) ;
if ( ! pSpawnable )
{
Warning ( " NULL Spawnable Ent in CASW_Queen! \n " ) ;
UTIL_Remove ( pNPC ) ;
return NULL ;
}
DispatchSpawn ( pNPC ) ;
pNPC - > SetOwnerEntity ( this ) ;
pNPC - > Activate ( ) ;
CASW_Parasite * pParasite = dynamic_cast < CASW_Parasite * > ( pNPC ) ;
if ( pParasite )
{
m_iCrittersAlive + + ;
pParasite - > SetMother ( this ) ;
//pParasite->DoJumpFromEgg();
}
return pNPC ;
}
void CASW_Queen : : ChildAlienKilled ( CASW_Alien * pAlien )
{
m_iCrittersAlive - - ;
}
bool CASW_Queen : : OnObstructionPreSteer ( AILocalMoveGoal_t * pMoveGoal ,
float distClear ,
AIMoveResult_t * pResult )
{
if ( pMoveGoal - > directTrace . pObstruction )
{
// check if we collide with a door or door padding
CASW_Sentry_Base * pSentry = dynamic_cast < CASW_Sentry_Base * > ( pMoveGoal - > directTrace . pObstruction ) ;
if ( pSentry )
{
m_hBlockingSentry = pSentry ;
return true ;
}
}
return false ;
}
// only hits NPCs and sentry gun
bool CASW_TraceFilterOnlyQueenTargets : : ShouldHitEntity ( IHandleEntity * pServerEntity , int contentsMask )
{
if ( CTraceFilterSimple : : ShouldHitEntity ( pServerEntity , contentsMask ) )
{
CBaseEntity * pEntity = EntityFromEntityHandle ( pServerEntity ) ;
CASW_Sentry_Base * pSentry = dynamic_cast < CASW_Sentry_Base * > ( pEntity ) ;
if ( pSentry )
return true ;
CBreakableProp * pProp = dynamic_cast < CBreakableProp * > ( pServerEntity ) ;
if ( pProp )
return true ;
return ( pEntity - > IsNPC ( ) | | pEntity - > IsPlayer ( ) ) ;
}
return false ;
}
AI_BEGIN_CUSTOM_NPC ( asw_queen , CASW_Queen )
// Tasks
DECLARE_TASK ( TASK_ASW_SUMMON_WAVE )
DECLARE_TASK ( TASK_ASW_SOUND_SUMMON )
DECLARE_TASK ( TASK_ASW_WAIT_DIVER )
DECLARE_TASK ( TASK_ASW_START_DIVER_ATTACK )
DECLARE_TASK ( TASK_ASW_GET_PATH_TO_RETREAT_SPOT )
DECLARE_TASK ( TASK_SPAWN_PARASITES )
DECLARE_TASK ( TASK_FACE_SENTRY )
DECLARE_TASK ( TASK_CLEAR_BLOCKING_SENTRY )
// Activities
DECLARE_ACTIVITY ( ACT_QUEEN_SCREAM )
DECLARE_ACTIVITY ( ACT_QUEEN_SCREAM_LOW )
DECLARE_ACTIVITY ( ACT_QUEEN_TRIPLE_SPIT )
DECLARE_ACTIVITY ( ACT_QUEEN_SINGLE_SPIT )
DECLARE_ACTIVITY ( ACT_QUEEN_LOW_IDLE )
DECLARE_ACTIVITY ( ACT_QUEEN_LOW_TO_HIGH )
DECLARE_ACTIVITY ( ACT_QUEEN_HIGH_TO_LOW )
DECLARE_ACTIVITY ( ACT_QUEEN_TENTACLE_ATTACK )
// Events
DECLARE_ANIMEVENT ( AE_QUEEN_SLASH_HIT )
DECLARE_ANIMEVENT ( AE_QUEEN_R_SLASH_HIT )
DECLARE_ANIMEVENT ( AE_QUEEN_START_SLASH )
DECLARE_ANIMEVENT ( AE_QUEEN_START_SPIT )
DECLARE_ANIMEVENT ( AE_QUEEN_SPIT )
// conditions
DECLARE_CONDITION ( COND_QUEEN_BLOCKED_BY_DOOR )
DEFINE_SCHEDULE
(
SCHED_QUEEN_RANGE_ATTACK ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_FACE_ENEMY 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_HIGH_TO_LOW "
" TASK_ANNOUNCE_ATTACK 1 " // 1 = primary attack
" TASK_RANGE_ATTACK1 0 "
" "
" Interrupts "
" COND_NEW_ENEMY "
" COND_ENEMY_DEAD "
" COND_LIGHT_DAMAGE "
" COND_HEAVY_DAMAGE "
" COND_ENEMY_OCCLUDED "
" COND_NO_PRIMARY_AMMO "
" COND_HEAR_DANGER "
" COND_WEAPON_BLOCKED_BY_FRIEND "
" COND_WEAPON_SIGHT_OCCLUDED "
)
DEFINE_SCHEDULE
(
SCHED_ASW_SUMMON_WAVE ,
" Tasks "
" TASK_FACE_ENEMY 0 "
" TASK_ASW_SOUND_SUMMON 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_SCREAM "
" TASK_ASW_SUMMON_WAVE 0 "
" "
" Interrupts "
)
DEFINE_SCHEDULE
(
SCHED_WAIT_DIVER ,
" Tasks "
//" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_LOW_IDLE"
" TASK_ASW_WAIT_DIVER 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_LOW_TO_HIGH "
" "
" Interrupts "
" COND_LIGHT_DAMAGE "
" COND_HEAVY_DAMAGE "
)
DEFINE_SCHEDULE
(
SCHED_START_DIVER_ATTACK ,
" Tasks "
" TASK_FACE_ENEMY 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_HIGH_TO_LOW "
" TASK_ASW_START_DIVER_ATTACK 0 "
" "
" Interrupts "
)
DEFINE_SCHEDULE
(
SCHED_ASW_RETREAT_AND_SUMMON ,
" Tasks "
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_SUMMON_WAVE "
" TASK_ASW_GET_PATH_TO_RETREAT_SPOT 0 "
" TASK_RUN_PATH 0 "
" TASK_WAIT_FOR_MOVEMENT 0 "
" TASK_FACE_ENEMY 0 "
" TASK_ASW_SOUND_SUMMON 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_SCREAM "
" TASK_ASW_SUMMON_WAVE 0 "
" "
" Interrupts "
" COND_TASK_FAILED "
)
DEFINE_SCHEDULE
(
SCHED_SMASH_SENTRY ,
" Tasks "
" TASK_FACE_SENTRY 0 "
" TASK_CLEAR_BLOCKING_SENTRY 0 "
" TASK_ANNOUNCE_ATTACK 1 " // 1 = primary attack
" TASK_MELEE_ATTACK1 0 "
" "
" Interrupts "
" COND_NEW_ENEMY "
" COND_ENEMY_DEAD "
" COND_LIGHT_DAMAGE "
" COND_HEAVY_DAMAGE "
" COND_ENEMY_OCCLUDED "
)
DEFINE_SCHEDULE
(
SCHED_ASW_SPAWN_PARASITES ,
" Tasks "
" TASK_STOP_MOVING 0 "
" TASK_FACE_ENEMY 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_HIGH_TO_LOW "
" TASK_SPAWN_PARASITES 0 "
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_QUEEN_LOW_TO_HIGH "
" "
" Interrupts "
//" COND_HEAVY_DAMAGE" // can't do this as we need to be sure the chest closes when leaving this schedule
)
AI_END_CUSTOM_NPC ( )