You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
775 lines
19 KiB
775 lines
19 KiB
#include "cbase.h" |
|
#include "asw_harvester.h" |
|
#include "te_effect_dispatch.h" |
|
#include "npc_bullseye.h" |
|
#include "npcevent.h" |
|
#include "asw_marine.h" |
|
#include "asw_parasite.h" |
|
#include "soundenvelope.h" |
|
//#include "npc_antlion.h" |
|
#include "ai_memory.h" |
|
#include "asw_gamerules.h" |
|
#include "asw_weapon.h" |
|
#include "asw_shareddefs.h" |
|
#include "asw_weapon_assault_shotgun_shared.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define ASW_HARVESTER_MAX_ATTACK_DISTANCE 1500 |
|
|
|
LINK_ENTITY_TO_CLASS( asw_harvester, CASW_Harvester ); |
|
|
|
float CASW_Harvester::s_fNextSpawnSoundTime = 0; |
|
float CASW_Harvester::s_fNextPainSoundTime = 0; |
|
|
|
BEGIN_DATADESC( CASW_Harvester ) |
|
DEFINE_FIELD( m_fLastLayTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_iCrittersAlive, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_fLastTouchHurtTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_fGibTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flIdleDelay, FIELD_TIME ), |
|
END_DATADESC() |
|
|
|
ConVar asw_harvester_speedboost( "asw_harvester_speedboost", "1.0",FCVAR_CHEAT , "boost speed for the harvesters" ); |
|
ConVar asw_harvester_max_critters( "asw_harvester_max_critters", "5",FCVAR_CHEAT , "maximum critters the harvester can spawn" ); |
|
ConVar asw_harvester_touch_damage( "asw_harvester_touch_damage", "5",FCVAR_CHEAT , "Damage caused by harvesters on touch" ); |
|
ConVar asw_harverter_suppress_children( "asw_harverter_suppress_children", "0", FCVAR_CHEAT, "If set to 1, harvesters won't spawn harvesites"); |
|
ConVar asw_harvester_new( "asw_harvester_new", "1", FCVAR_CHEAT, "If set to 1, use the new model"); |
|
ConVar asw_harvester_spawn_height( "asw_harvester_spawn_height", "16", FCVAR_CHEAT, "Height above harvester origin to spawn harvesites at" ); |
|
ConVar asw_harvester_spawn_interval( "asw_harvester_spawn_interval", "1.0", FCVAR_CHEAT, "Time between spawning a harvesite and starting to spawn another" ); |
|
|
|
// Anim Events |
|
int AE_HARVESTER_SPAWN_CRITTER; |
|
int AE_HARVESTER_SPAWN_SOUND; |
|
|
|
// Activities |
|
int ACT_HARVESTER_LAY_CRITTER; |
|
|
|
CASW_Harvester::CASW_Harvester() |
|
{ |
|
m_iCrittersAlive = 0; |
|
m_fLastLayTime = 0; |
|
m_fLastTouchHurtTime = 0; |
|
if ( asw_harvester_new.GetBool() ) |
|
m_pszAlienModelName = SWARM_NEW_HARVESTER_MODEL; |
|
else |
|
m_pszAlienModelName = SWARM_HARVESTER_MODEL; |
|
m_nAlienCollisionGroup = ASW_COLLISION_GROUP_ALIEN; |
|
} |
|
|
|
CASW_Harvester::~CASW_Harvester() |
|
{ |
|
} |
|
|
|
void CASW_Harvester::Spawn( void ) |
|
{ |
|
SetHullType(HULL_WIDE_SHORT); |
|
|
|
BaseClass::Spawn(); |
|
|
|
SetHullType(HULL_WIDE_SHORT); |
|
UTIL_SetSize(this, Vector(-23,-23,0), Vector(23,23,69)); |
|
|
|
m_iHealth = ASWGameRules()->ModifyAlienHealthBySkillLevel(200); |
|
|
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 ); |
|
|
|
m_takedamage = DAMAGE_NO; // alien is invulnerable until she finds her first enemy |
|
m_bNeverRagdoll = true; |
|
} |
|
|
|
void CASW_Harvester::Precache( void ) |
|
{ |
|
PrecacheScriptSound( "ASW_Harvester.Death" ); |
|
PrecacheScriptSound( "ASW_Harvester.Pain" ); |
|
PrecacheScriptSound( "ASW_Harvester.Scared" ); |
|
PrecacheScriptSound( "ASW_Harvester.SpawnCritter" ); |
|
PrecacheScriptSound( "ASW_Harvester.Alert" ); |
|
PrecacheScriptSound( "ASW_Harvester.Sniffing" ); |
|
|
|
UTIL_PrecacheOther( "asw_parasite_defanged" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
float CASW_Harvester::GetIdealSpeed() const |
|
{ |
|
return asw_harvester_speedboost.GetFloat() * BaseClass::GetIdealSpeed() * m_flPlaybackRate; |
|
} |
|
|
|
|
|
float CASW_Harvester::GetIdealAccel( ) const |
|
{ |
|
return GetIdealSpeed() * 1.5f; |
|
} |
|
|
|
float CASW_Harvester::MaxYawSpeed( void ) |
|
{ |
|
if ( m_bElectroStunned.Get() ) |
|
return 0.1f; |
|
|
|
return 32.0f; |
|
|
|
switch ( GetActivity() ) |
|
{ |
|
case ACT_IDLE: |
|
return 64.0f; |
|
break; |
|
|
|
case ACT_WALK: |
|
return 64.0f; |
|
break; |
|
|
|
default: |
|
case ACT_RUN: |
|
return 64.0f; |
|
break; |
|
} |
|
|
|
return 64.0f; |
|
} |
|
|
|
void CASW_Harvester::AlertSound() |
|
{ |
|
EmitSound("ASW_Harvester.Alert"); |
|
} |
|
|
|
void CASW_Harvester::PainSound( const CTakeDamageInfo &info ) |
|
{ |
|
if (gpGlobals->curtime > m_fNextPainSound && gpGlobals->curtime > s_fNextPainSoundTime) |
|
{ |
|
m_fNextPainSound = gpGlobals->curtime + 0.5f; |
|
s_fNextPainSoundTime = gpGlobals->curtime + 1.0f; |
|
EmitSound("ASW_Harvester.Pain"); |
|
} |
|
} |
|
|
|
void CASW_Harvester::AttackSound() |
|
{ |
|
if (gpGlobals->curtime > s_fNextSpawnSoundTime) |
|
{ |
|
EmitSound("ASW_Harvester.SpawnCritter"); |
|
s_fNextSpawnSoundTime = gpGlobals->curtime + 2.0f; |
|
} |
|
} |
|
|
|
void CASW_Harvester::IdleSound() |
|
{ |
|
EmitSound("ASW_Harvester.Sniffing"); |
|
m_flIdleDelay = gpGlobals->curtime + 4.0f; |
|
} |
|
|
|
void CASW_Harvester::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
EmitSound("ASW_Harvester.Death"); |
|
} |
|
|
|
// make the harvester look at his enemy |
|
bool CASW_Harvester::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ) |
|
{ |
|
Vector vecFacePosition = vec3_origin; |
|
CBaseEntity *pFaceTarget = NULL; |
|
bool bFaceTarget = false; |
|
|
|
if ( GetEnemy() && GetNavigator()->GetMovementActivity() == ACT_RUN ) |
|
{ |
|
Vector vecEnemyLKP = GetEnemyLKP(); |
|
|
|
// Only start facing when we're close enough |
|
if ( ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < 1500 ) ) |
|
{ |
|
vecFacePosition = vecEnemyLKP; |
|
pFaceTarget = GetEnemy(); |
|
bFaceTarget = true; |
|
} |
|
} |
|
|
|
// Face |
|
if ( bFaceTarget ) |
|
{ |
|
AddFacingTarget( pFaceTarget, vecFacePosition, 1.0, 0.2 ); |
|
} |
|
|
|
return BaseClass::OverrideMoveFacing( move, flInterval ); |
|
} |
|
|
|
void CASW_Harvester::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
int nEvent = pEvent->Event(); |
|
|
|
if ( nEvent == AE_HARVESTER_SPAWN_CRITTER ) |
|
{ |
|
// The point in our laying animation where we should actually spawn the critter |
|
SpawnAlien(); |
|
float spawn_interval = asw_harvester_spawn_interval.GetFloat(); |
|
m_flNextAttack = gpGlobals->curtime + spawn_interval; |
|
return; |
|
} |
|
if ( nEvent == AE_HARVESTER_SPAWN_SOUND ) |
|
{ |
|
AttackSound(); |
|
return; |
|
} |
|
|
|
BaseClass::HandleAnimEvent( pEvent ); |
|
} |
|
|
|
// harvester can attack without LOS so long as they're near enough |
|
bool CASW_Harvester::WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions) |
|
{ |
|
if (targetPos.DistTo(ownerPos) < ASW_HARVESTER_MAX_ATTACK_DISTANCE) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
bool CASW_Harvester::FCanCheckAttacks() |
|
{ |
|
if ( GetNavType() == NAV_CLIMB || GetNavType() == NAV_JUMP ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: For innate melee attack |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
float CASW_Harvester::InnateRange1MinRange( void ) |
|
{ |
|
return 0; |
|
} |
|
|
|
float CASW_Harvester::InnateRange1MaxRange( void ) |
|
{ |
|
return ASW_HARVESTER_MAX_ATTACK_DISTANCE; |
|
} |
|
|
|
// make sure the harvester backs away when he's near us (he should sit back and lay critters) |
|
int CASW_Harvester::RangeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
if (GetEnemy() == NULL) |
|
{ |
|
return( COND_NONE ); |
|
} |
|
|
|
if ( gpGlobals->curtime < m_flNextAttack ) |
|
{ |
|
return( COND_NONE ); |
|
} |
|
|
|
if ( m_iCrittersAlive > asw_harvester_max_critters.GetInt() ) |
|
return( COND_NONE ); |
|
|
|
float flTooClose = InnateRange1MinRange(); |
|
if ( flDist > ASW_HARVESTER_MAX_ATTACK_DISTANCE ) |
|
{ |
|
return( COND_TOO_FAR_TO_ATTACK ); |
|
} |
|
else if ( flDist < flTooClose ) |
|
{ |
|
return( COND_TOO_CLOSE_TO_ATTACK ); |
|
} |
|
else if ( flDot < 0.65 ) |
|
{ |
|
return( COND_NOT_FACING_ATTACK ); |
|
} |
|
|
|
return( COND_CAN_RANGE_ATTACK1 ); |
|
} |
|
|
|
int CASW_Harvester::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 SelectHarvesterCombatSchedule(); |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
int CASW_Harvester::SelectHarvesterCombatSchedule() |
|
{ |
|
int nSched = SelectFlinchSchedule_ASW(); |
|
if ( nSched != SCHED_NONE ) |
|
{ |
|
// if we flinch, push forward the next attack |
|
float spawn_interval = 2.0f; |
|
m_flNextAttack = gpGlobals->curtime + spawn_interval; |
|
return nSched; |
|
} |
|
|
|
if ( HasCondition(COND_NEW_ENEMY) && gpGlobals->curtime - GetEnemies()->FirstTimeSeen(GetEnemy()) < 2.0 ) |
|
{ |
|
return SCHED_WAKE_ANGRY; |
|
} |
|
|
|
if ( HasCondition( COND_ENEMY_DEAD ) ) |
|
{ |
|
// clear the current (dead) enemy and try to find another. |
|
SetEnemy( NULL ); |
|
|
|
if ( ChooseEnemy() ) |
|
{ |
|
ClearCondition( COND_ENEMY_DEAD ); |
|
return SelectSchedule(); |
|
} |
|
|
|
SetState( NPC_STATE_ALERT ); |
|
return SelectSchedule(); |
|
} |
|
#ifdef ASW_FEARFUL_HARVESTERS |
|
// If I'm scared of this enemy run away |
|
if ( IRelationType( GetEnemy() ) == D_FR ) |
|
{ |
|
if (HasCondition( COND_SEE_ENEMY ) || |
|
HasCondition( COND_LIGHT_DAMAGE )|| |
|
HasCondition( COND_HEAVY_DAMAGE )) |
|
{ |
|
FearSound(); |
|
//ClearCommandGoal(); |
|
return SCHED_RUN_FROM_ENEMY; |
|
} |
|
|
|
// If I've seen the enemy recently, cower. Ignore the time for unforgettable enemies. |
|
AI_EnemyInfo_t *pMemory = GetEnemies()->Find( GetEnemy() ); |
|
if ( (pMemory && pMemory->bUnforgettable) || (GetEnemyLastTimeSeen() > (gpGlobals->curtime - 5.0)) ) |
|
{ |
|
// If we're facing him, just look ready. Otherwise, face him. |
|
if ( FInAimCone( GetEnemy()->EyePosition() ) ) |
|
return SCHED_COMBAT_STAND; |
|
|
|
return SCHED_FEAR_FACE; |
|
} |
|
} |
|
#endif |
|
// Check if need to reload |
|
if ( HasCondition( COND_LOW_PRIMARY_AMMO ) || HasCondition( COND_NO_PRIMARY_AMMO ) ) |
|
{ |
|
return SCHED_HIDE_AND_RELOAD; |
|
} |
|
|
|
#ifdef ASW_FEARFUL_HARVESTERS |
|
if ( HasCondition(COND_TOO_CLOSE_TO_ATTACK) ) |
|
return SCHED_RUN_FROM_ENEMY; //SCHED_BACK_AWAY_FROM_ENEMY; |
|
#endif |
|
|
|
if ( GetShotRegulator()->IsInRestInterval() ) |
|
{ |
|
if ( HasCondition(COND_CAN_RANGE_ATTACK1) ) |
|
return SCHED_COMBAT_FACE; |
|
} |
|
|
|
// 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; |
|
|
|
if ( HasCondition(COND_CAN_MELEE_ATTACK1) ) |
|
return SCHED_MELEE_ATTACK1; |
|
|
|
if ( HasCondition(COND_CAN_MELEE_ATTACK2) ) |
|
return SCHED_MELEE_ATTACK2; |
|
|
|
if ( HasCondition(COND_NOT_FACING_ATTACK) ) |
|
return SCHED_COMBAT_FACE; |
|
|
|
// if we're not attacking, then back away |
|
return SCHED_RUN_FROM_ENEMY; |
|
} |
|
|
|
int CASW_Harvester::TranslateSchedule( int scheduleType ) |
|
{ |
|
if ( scheduleType == SCHED_RANGE_ATTACK1 ) |
|
{ |
|
RemoveAllGestures(); |
|
return SCHED_ASW_HARVESTER_LAY_CRITTER; |
|
} |
|
|
|
if ( scheduleType == SCHED_COMBAT_FACE && IsUnreachable( GetEnemy() ) ) |
|
return SCHED_TAKE_COVER_FROM_ENEMY; |
|
|
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
|
|
CAI_BaseNPC* CASW_Harvester::SpawnAlien() |
|
{ |
|
if (asw_harverter_suppress_children.GetBool()) |
|
return NULL; |
|
|
|
CBaseEntity *pEntity = CreateEntityByName( "asw_parasite_defanged" ); |
|
CAI_BaseNPC *pNPC = dynamic_cast<CAI_BaseNPC*>(pEntity); |
|
|
|
if ( !pNPC ) |
|
{ |
|
Warning("NULL Ent in CASW_Harvester!\n"); |
|
return NULL; |
|
} |
|
|
|
pNPC->AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); |
|
|
|
pNPC->SetAbsOrigin( GetAbsOrigin() + Vector( 0, 0, asw_harvester_spawn_height.GetFloat() ) ); |
|
|
|
// 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; |
|
pNPC->SetAbsAngles( angles ); |
|
|
|
IASW_Spawnable_NPC* pSpawnable = dynamic_cast<IASW_Spawnable_NPC*>(pNPC); |
|
ASSERT(pSpawnable); |
|
if ( !pSpawnable ) |
|
{ |
|
Warning("NULL Spawnable Ent in CASW_Harvester!\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); |
|
} |
|
|
|
return pNPC; |
|
} |
|
|
|
void CASW_Harvester::ChildAlienKilled(CASW_Alien* pParasite) |
|
{ |
|
m_iCrittersAlive--; |
|
} |
|
|
|
void CASW_Harvester::StartTouch( CBaseEntity *pOther ) |
|
{ |
|
BaseClass::StartTouch( pOther ); |
|
|
|
CASW_Marine *pMarine = CASW_Marine::AsMarine( pOther ); |
|
if (pMarine) |
|
{ |
|
// don't hurt him if he was hurt recently |
|
if (m_fLastTouchHurtTime + 0.6f > gpGlobals->curtime) |
|
{ |
|
return; |
|
} |
|
// hurt the marine |
|
Vector vecForceDir = (pMarine->GetAbsOrigin() - GetAbsOrigin()); |
|
CTakeDamageInfo info( this, this, asw_harvester_touch_damage.GetInt(), DMG_SLASH ); |
|
CalculateMeleeDamageForce( &info, vecForceDir, pMarine->GetAbsOrigin() ); |
|
pMarine->TakeDamage( info ); |
|
m_fLastTouchHurtTime = gpGlobals->curtime; |
|
} |
|
} |
|
|
|
bool CASW_Harvester::ShouldGib( const CTakeDamageInfo &info ) |
|
{ |
|
return false; |
|
} |
|
|
|
int CASW_Harvester::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; |
|
} |
|
|
|
int CASW_Harvester::SelectFlinchSchedule_ASW() |
|
{ |
|
// only flinch in easy mode |
|
if (ASWGameRules() && ASWGameRules()->GetSkillLevel() != 1) |
|
return SCHED_NONE; |
|
|
|
if ( !HasCondition(COND_HEAVY_DAMAGE) && !HasCondition(COND_LIGHT_DAMAGE) ) |
|
return SCHED_NONE; |
|
|
|
if ( IsCurSchedule( SCHED_BIG_FLINCH ) ) |
|
return SCHED_NONE; |
|
|
|
// only flinch if shot during a laying a critter |
|
if (! (GetTask() && (GetTask()->iTask == TASK_LAY_CRITTER)) ) |
|
return SCHED_NONE; |
|
|
|
// Any damage. Break out of my current schedule and flinch. |
|
Activity iFlinchActivity = GetFlinchActivity( true, false ); |
|
if ( HaveSequenceForActivity( iFlinchActivity ) ) |
|
return SCHED_BIG_FLINCH; |
|
|
|
return SCHED_NONE; |
|
} |
|
|
|
void CASW_Harvester::BuildScheduleTestBits() |
|
{ |
|
BaseClass::BuildScheduleTestBits(); |
|
|
|
if ( IsCurSchedule( SCHED_RUN_FROM_ENEMY ) ) |
|
{ |
|
SetCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 ); |
|
} |
|
} |
|
|
|
void CASW_Harvester::StartTask( const Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_LAY_CRITTER: |
|
{ |
|
ResetIdealActivity((Activity) ACT_HARVESTER_LAY_CRITTER); |
|
break; |
|
} |
|
default: |
|
BaseClass::StartTask( pTask ); |
|
break; |
|
} |
|
} |
|
|
|
void CASW_Harvester::RunTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_LAY_CRITTER: |
|
{ |
|
if (IsActivityFinished()) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
default: |
|
{ |
|
BaseClass::RunTask(pTask); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// only shock damage counts as heavy (and thus causes a flinch even during normal running) |
|
bool CASW_Harvester::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; |
|
|
|
CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(info.GetAttacker()); |
|
if (pMarine && pMarine->GetActiveASWWeapon()) |
|
{ |
|
return pMarine->GetActiveASWWeapon()->ShouldAlienFlinch(this, info); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void CASW_Harvester::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
BaseClass::Event_Killed(info); |
|
|
|
// spawn a bunch of harvesites |
|
int iNumParasites = 4 + RandomInt(0,2); |
|
QAngle angParasiteFacing[6]; |
|
float fJumpDistance[6]; |
|
// for some reason if we calculate these inside the loop, the random numbers all come out the same. Worrying. |
|
angParasiteFacing[0] = GetAbsAngles(); angParasiteFacing[0].y = RandomFloat( -180.0f, 180.0f ); |
|
angParasiteFacing[1] = GetAbsAngles(); angParasiteFacing[1].y = RandomFloat( -180.0f, 180.0f ); |
|
angParasiteFacing[2] = GetAbsAngles(); angParasiteFacing[2].y = RandomFloat( -180.0f, 180.0f ); |
|
angParasiteFacing[3] = GetAbsAngles(); angParasiteFacing[3].y = RandomFloat( -180.0f, 180.0f ); |
|
angParasiteFacing[4] = GetAbsAngles(); angParasiteFacing[4].y = RandomFloat( -180.0f, 180.0f ); |
|
angParasiteFacing[5] = GetAbsAngles(); angParasiteFacing[5].y = RandomFloat( -180.0f, 180.0f ); |
|
fJumpDistance[0] = RandomFloat( 30.0f, 70.0f ); |
|
fJumpDistance[1] = RandomFloat( 30.0f, 70.0f ); |
|
fJumpDistance[2] = RandomFloat( 30.0f, 70.0f ); |
|
fJumpDistance[3] = RandomFloat( 30.0f, 70.0f ); |
|
fJumpDistance[4] = RandomFloat( 30.0f, 70.0f ); |
|
fJumpDistance[5] = RandomFloat( 30.0f, 70.0f ); |
|
|
|
for ( int i = 0; i < iNumParasites; i++ ) |
|
{ |
|
bool bBlocked = true; |
|
int k = 0; |
|
|
|
Vector vecSpawnPos = GetAbsOrigin(); |
|
float fCircleDegree = ( static_cast< float >( i ) / iNumParasites ) * 2.0f * M_PI; |
|
vecSpawnPos.x += sinf( fCircleDegree ) * RandomFloat( 3.0f, 20.0f ); |
|
vecSpawnPos.y += cosf( fCircleDegree ) * RandomFloat( 3.0f, 20.0f ); |
|
vecSpawnPos.z += RandomFloat( 20.0f, 40.0f ); |
|
|
|
while ( bBlocked && k < 6 ) |
|
{ |
|
if ( k > 0 ) |
|
{ |
|
// Scooch it up |
|
vecSpawnPos.z += NAI_Hull::Maxs( HULL_TINY ).z - NAI_Hull::Mins( HULL_TINY ).z; |
|
} |
|
|
|
// check if there's room at this position |
|
trace_t tr; |
|
UTIL_TraceHull( vecSpawnPos, vecSpawnPos + Vector( 0.0f, 0.0f, 1.0f ), |
|
NAI_Hull::Mins(HULL_TINY) + Vector( -4.0f, -4.0f, -4.0f ),NAI_Hull::Maxs(HULL_TINY) + Vector( 4.0f, 4.0f, 4.0f ), |
|
MASK_NPCSOLID, this, ASW_COLLISION_GROUP_PARASITE, &tr ); |
|
|
|
//NDebugOverlay::Box(vecSpawnPos[i], NAI_Hull::Mins(HULL_TINY),NAI_Hull::Maxs(HULL_TINY), 255,255,0,255,15.0f); |
|
if ( tr.fraction == 1.0 ) |
|
{ |
|
bBlocked = false; |
|
} |
|
|
|
k++; |
|
} |
|
|
|
if ( bBlocked ) |
|
continue; // couldn't find room for parasites |
|
|
|
CASW_Parasite *pParasite = dynamic_cast< CASW_Parasite* >( CreateNoSpawn("asw_parasite_defanged", |
|
vecSpawnPos, angParasiteFacing[i], this)); |
|
|
|
if ( pParasite ) |
|
{ |
|
DispatchSpawn( pParasite ); |
|
pParasite->SetSleepState(AISS_WAITING_FOR_INPUT); |
|
pParasite->SetJumpFromEgg(true, fJumpDistance[i]); |
|
pParasite->Wake(); |
|
|
|
if ( IsOnFire() ) |
|
{ |
|
pParasite->ASW_Ignite( 30.0f, 0, info.GetAttacker(), info.GetWeapon() ); |
|
} |
|
} |
|
} |
|
|
|
m_fGibTime = gpGlobals->curtime + random->RandomFloat(20.0f, 30.0f); |
|
} |
|
|
|
int CASW_Harvester::OnTakeDamage_Alive( const CTakeDamageInfo &info ) |
|
{ |
|
CTakeDamageInfo newInfo(info); |
|
|
|
float damage = info.GetDamage(); |
|
// reduce damage from shotguns and mining laser |
|
if (info.GetDamageType() & DMG_ENERGYBEAM) |
|
{ |
|
damage *= 0.4f; |
|
} |
|
if (info.GetDamageType() & DMG_BUCKSHOT) |
|
{ |
|
// hack to reduce vindicator damage (not reducing normal shotty as much as it's not too strong) |
|
if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE) |
|
{ |
|
CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(info.GetAttacker()); |
|
if (pMarine) |
|
{ |
|
CASW_Weapon_Assault_Shotgun *pVindicator = dynamic_cast<CASW_Weapon_Assault_Shotgun*>(pMarine->GetActiveASWWeapon()); |
|
if (pVindicator) |
|
damage *= 0.45f; |
|
else |
|
damage *= 0.6f; |
|
} |
|
} |
|
} |
|
|
|
newInfo.SetDamage(damage); |
|
|
|
return BaseClass::OnTakeDamage_Alive(newInfo); |
|
} |
|
|
|
void CASW_Harvester::NPCThink() |
|
{ |
|
BaseClass::NPCThink(); |
|
|
|
if (m_fGibTime != 0 && gpGlobals->curtime > m_fGibTime) |
|
{ |
|
CEffectData data; |
|
|
|
data.m_vOrigin = WorldSpaceCenter(); |
|
data.m_vNormal = Vector(0,0,1); |
|
|
|
data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 ); |
|
data.m_flScale = clamp( data.m_flScale, 1, 3 ); |
|
data.m_nColor = 1; |
|
data.m_fFlags = IsOnFire() ? ASW_GIBFLAG_ON_FIRE : 0; |
|
|
|
DispatchEffect( "HarvesterGib", data ); |
|
UTIL_Remove( this ); |
|
SetThink( NULL ); |
|
} |
|
} |
|
|
|
|
|
bool CASW_Harvester::ShouldPlayIdleSound( void ) |
|
{ |
|
return false; // asw temp |
|
|
|
//Only do idles in the right states |
|
if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) ) |
|
return false; |
|
|
|
//Gagged monsters don't talk |
|
if ( m_spawnflags & SF_NPC_GAG ) |
|
return false; |
|
|
|
//Don't cut off another sound or play again too soon |
|
if ( m_flIdleDelay > gpGlobals->curtime ) |
|
return false; |
|
|
|
//Randomize it a bit |
|
if ( random->RandomInt( 0, 20 ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
bool CASW_Harvester::CanBePushedAway() |
|
{ |
|
return ( ( gpGlobals->curtime - m_fLastLayTime ) > 2.0f ) && BaseClass::CanBePushedAway(); |
|
} |
|
|
|
AI_BEGIN_CUSTOM_NPC( asw_harvester, CASW_Harvester ) |
|
|
|
// Tasks |
|
DECLARE_TASK( TASK_LAY_CRITTER ) |
|
// Activities |
|
DECLARE_ACTIVITY( ACT_HARVESTER_LAY_CRITTER ) |
|
// Events |
|
DECLARE_ANIMEVENT( AE_HARVESTER_SPAWN_CRITTER ) |
|
DECLARE_ANIMEVENT( AE_HARVESTER_SPAWN_SOUND ) |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_ASW_HARVESTER_LAY_CRITTER, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack |
|
" TASK_LAY_CRITTER 0" |
|
"" |
|
" Interrupts" |
|
//" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
AI_END_CUSTOM_NPC()
|
|
|