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

1770 lines
49 KiB
C++
Raw Normal View History

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()