#include "cbase.h" #include "asw_alien.h" #include "asw_ai_senses.h" #include "asw_game_resource.h" #include "asw_gamerules.h" #include "asw_mission_manager.h" #include "asw_marine_resource.h" #include "asw_marine.h" #include "asw_trace_filter_melee.h" #include "asw_spawner.h" #include "ai_waypoint.h" #include "ai_moveprobe.h" #include "asw_fx_shared.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "EntityFlame.h" #include "asw_util_shared.h" #include "asw_burning.h" #include "asw_door.h" #include "asw_gamestats.h" #include "asw_pickup_money.h" #include "asw_director.h" #include "asw_physics_prop_statue.h" #include "te_effect_dispatch.h" #include "asw_ai_behavior.h" #include "asw_ai_behavior_combat_stun.h" #include "asw_ai_behavior_sleep.h" #include "asw_ai_behavior_flinch.h" #include "asw_missile_round_shared.h" #include "datacache/imdlcache.h" #include "asw_tesla_trap.h" #include "sendprop_priorities.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" extern ConVar asw_debug_alien_damage; extern ConVar ai_show_hull_attacks; extern ConVar ai_efficiency_override; extern ConVar ai_frametime_limit; extern ConVar ai_use_think_optimizations; extern ConVar ai_use_efficiency; extern ConVar showhitlocation; extern ConVar asw_stun_grenade_time; extern ConVar asw_drone_zig_zagging; extern ConVar asw_draw_awake_ai; extern ConVar asw_alien_debug_death_style; // asw - how much extra damage to do to burning aliens ConVar asw_fire_alien_damage_scale("asw_fire_alien_damage_scale", "3.0", FCVAR_CHEAT ); ConVar asw_alien_speed_scale_easy("asw_alien_speed_scale_easy", "0"); ConVar asw_alien_speed_scale_normal("asw_alien_speed_scale_normal", "0"); ConVar asw_alien_speed_scale_hard("asw_alien_speed_scale_hard", "0"); ConVar asw_alien_speed_scale_insane("asw_alien_speed_scale_insane", "0"); ConVar asw_alien_hurt_speed( "asw_alien_hurt_speed", "0.5", FCVAR_CHEAT, "Fraction of speed to use when the alien is hurt after being shot" ); ConVar asw_alien_stunned_speed( "asw_alien_stunned_speed", "0.3", FCVAR_CHEAT, "Fraction of speed to use when the alien is electrostunned" ); ConVar asw_drop_money("asw_drop_money", "1", FCVAR_CHEAT, "Do aliens drop money?"); ConVar asw_alien_money_chance("asw_alien_money_chance", "1.0", FCVAR_CHEAT, "Chance of base aliens dropping money"); ConVar asw_drone_hurl_chance( "asw_drone_hurl_chance", "0.66666666666666", FCVAR_NONE, "Chance that an alien killed by explosives will hurl towards the camera." ); ConVar asw_drone_hurl_interval( "asw_drone_hurl_interval", "10", FCVAR_NONE, "Minimum number of seconds that must pass between alien bodies flung at camera." ); ConVar asw_alien_break_chance ( "asw_alien_break_chance", "0.5", FCVAR_NONE, "chance the alien will break into ragdoll pieces instead of gib"); ConVar asw_alien_fancy_death_chance ( "asw_alien_fancy_death_chance", "0.5", FCVAR_NONE, "If a drone doesn't instagib, this is the chance the alien plays a death anim before ragdolling" ); ConVar asw_debug_npcs( "asw_debug_npcs", "0", FCVAR_CHEAT, "Enables debug overlays for various NPCs" ); ConVar asw_alien_burn_duration( "asw_alien_burn_duration", "5.0f", FCVAR_CHEAT, "Alien burn time" ); extern ConVar asw_money; // soft alien collision ConVar asw_springcol( "asw_springcol", "1", FCVAR_CHEAT, "Use soft alien collision" ); ConVar asw_springcol_push_cap( "asw_springcol_push_cap", "33.0", FCVAR_CHEAT, "Cap on the total push vector" ); ConVar asw_springcol_core( "asw_springcol_core", "0.33", FCVAR_CHEAT, "Fraction of the alien's pushaway radius that is a solid capped core" ); ConVar asw_springcol_radius( "asw_springcol_radius", "50.0", FCVAR_CHEAT, "Radius of the alien's pushaway cylinder" ); ConVar asw_springcol_force_scale( "asw_springcol_force_scale", "3.0", FCVAR_CHEAT, "Multiplier for each individual push force" ); ConVar asw_springcol_debug( "asw_springcol_debug", "0", FCVAR_CHEAT, "Display the direction of the pushaway vector. Set to entity index or -1 to show all." ); float CASW_Alien::sm_flLastHurlTime = 0; int ACT_MELEE_ATTACK1_HIT; int ACT_MELEE_ATTACK2_HIT; int AE_ALIEN_MELEE_HIT; int ACT_ALIEN_FLINCH_SMALL; int ACT_ALIEN_FLINCH_MEDIUM; int ACT_ALIEN_FLINCH_BIG; int ACT_ALIEN_FLINCH_GESTURE; int ACT_DEATH_FIRE; int ACT_DEATH_ELEC; int ACT_DIE_FANCY; int ACT_BURROW_IDLE; int ACT_BURROW_OUT; LINK_ENTITY_TO_CLASS( asw_alien, CASW_Alien ); IMPLEMENT_SERVERCLASS_ST(CASW_Alien, DT_ASW_Alien) SendPropExclude ( "DT_BaseEntity", "m_vecOrigin" ), SendPropVectorXY( SENDINFO( m_vecOrigin ), CELL_BASEENTITY_ORIGIN_CELL_BITS, SPROP_CELL_COORD_LOWPRECISION | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, CBaseEntity::SendProxy_CellOriginXY, SENDPROP_NONLOCALPLAYER_ORIGINXY_PRIORITY ), SendPropFloat ( SENDINFO_VECTORELEM( m_vecOrigin, 2 ), CELL_BASEENTITY_ORIGIN_CELL_BITS, SPROP_CELL_COORD_LOWPRECISION | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, CBaseEntity::SendProxy_CellOriginZ, SENDPROP_NONLOCALPLAYER_ORIGINZ_PRIORITY ), SendPropExclude( "DT_BaseEntity", "m_angRotation" ), SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesX ), SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesY ), SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesZ ), // test //SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ), SendPropBool( SENDINFO(m_bElectroStunned) ),// not using ElectroStunned //SendPropBool( SENDINFO(m_bElectroShockSmall) ), //SendPropBool( SENDINFO(m_bElectroShockBig) ), SendPropBool( SENDINFO(m_bOnFire) ), SendPropInt( SENDINFO(m_nDeathStyle), CASW_Alien::kDEATHSTYLE_NUM_TRANSMIT_BITS , SPROP_UNSIGNED ), SendPropInt (SENDINFO(m_iHealth), ASW_ALIEN_HEALTH_BITS ), //SendPropBool(SENDINFO(m_bGibber)), END_SEND_TABLE() BEGIN_DATADESC( CASW_Alien ) DEFINE_KEYFIELD( m_bVisibleWhenAsleep, FIELD_BOOLEAN, "visiblewhenasleep" ), DEFINE_KEYFIELD( m_iMoveCloneName, FIELD_STRING, "MoveClone" ), DEFINE_KEYFIELD( m_bStartBurrowed, FIELD_BOOLEAN, "startburrowed" ), DEFINE_INPUTFUNC( FIELD_VOID, "BreakWaitForScript", InputBreakWaitForScript ), DEFINE_INPUTFUNC( FIELD_STRING, "SetMoveClone", InputSetMoveClone ), DEFINE_FIELD( m_flBurrowTime, FIELD_TIME ), DEFINE_FIELD(m_bIgnoreMarines, FIELD_BOOLEAN), DEFINE_FIELD(m_bFailedMoveTo, FIELD_BOOLEAN), DEFINE_FIELD(m_bElectroStunned, FIELD_BOOLEAN), //DEFINE_FIELD(m_bPerformingZigZag, FIELD_BOOLEAN), // don't store this, let the zig zag be cleared each time //DEFINE_FIELD(m_bRunAtChasingPathEnds, FIELD_BOOLEAN), // no need to store currently, it's always true form constructor DEFINE_FIELD(m_fNextPainSound, FIELD_FLOAT), DEFINE_FIELD(m_fNextStunSound, FIELD_FLOAT), DEFINE_FIELD(m_fHurtSlowMoveTime, FIELD_TIME), // m_ActBusyBehavior (auto saved by AI) DEFINE_FIELD( m_hSpawner, FIELD_EHANDLE ), DEFINE_FIELD( m_AlienOrders, FIELD_INTEGER ), DEFINE_FIELD( m_vecAlienOrderSpot, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_AlienOrderObject, FIELD_EHANDLE ), DEFINE_FIELD( m_fLastSleepCheckTime, FIELD_FLOAT ), DEFINE_FIELD( m_bOnFire, FIELD_BOOLEAN ), DEFINE_FIELD( m_iNumASWOrderRetries, FIELD_INTEGER ), DEFINE_FIELD( m_flFreezeResistance, FIELD_FLOAT ), DEFINE_FIELD( m_flFrozenTime, FIELD_TIME ), // soft alien collision DEFINE_FIELD( m_vecLastPushAwayOrigin, FIELD_VECTOR ), DEFINE_FIELD( m_vecLastPush, FIELD_VECTOR ), DEFINE_FIELD( m_bPushed, FIELD_BOOLEAN ), DEFINE_FIELD( m_bHoldoutAlien, FIELD_BOOLEAN ), END_DATADESC() IMPLEMENT_AUTO_LIST( IAlienAutoList ); #define ASW_ALIEN_SLEEP_CHECK_INTERVAL 1.0f CASW_Alien::CASW_Alien( void ) : m_BehaviorParms( DefLessFunc( const CUtlSymbol ) ) { m_pszAlienModelName = NULL; m_bRunAtChasingPathEnds = true; m_bPerformingZigZag = false; m_fNextPainSound = 0; m_fNextStunSound = 0; m_fHurtSlowMoveTime = 0; m_hSpawner = NULL; m_AlienOrders = AOT_None; m_vecAlienOrderSpot = vec3_origin; m_AlienOrderObject = NULL; m_bIgnoreMarines = false; m_fLastSleepCheckTime = 0; m_bVisibleWhenAsleep = false; m_fLastMarineCanSeeTime = -100; m_bLastMarineCanSee = false; //m_bGibber = false; m_bTimeToRagdoll = false; m_iDeadBodyGroup = 1; m_bNeverRagdoll = false; m_bNeverInstagib = false; m_nDeathStyle = kDIE_RAGDOLLFADE; m_flBaseThawRate = 0.5f; m_flFrozenTime = 0.0f; m_UnburrowActivity = (Activity) ACT_BURROW_OUT; m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE; m_iszUnburrowActivityName = NULL_STRING; m_iszUnburrowIdleActivityName = NULL_STRING; m_pPreviousBehavior = NULL; m_vecLastPush.Init(); m_bBehaviorParameterChanged = false; m_nVolleyType = -1; m_flRangeAttackStartTime = 0.0f; m_flRangeAttackLastUpdateTime = 0.0f; m_vecRangeAttackTargetPosition.Init(); m_bHoldoutAlien = false; m_nAlienCollisionGroup = COLLISION_GROUP_NPC; meleeAttack1.Init( 0.0f, 64.0f, 0.7f, false ); meleeAttack2.Init( 0.0f, 64.0f, 0.7f, false ); rangeAttack1.Init( 64.0f, 786.0f, 0.5f, false ); rangeAttack2.Init( 64.0f, 512.0f, 0.5f, false ); } CASW_Alien::~CASW_Alien() { for( int i = 0; i < m_Behaviors.Count(); i++ ) { if ( m_Behaviors[ i ]->IsAllocated() ) { delete m_Behaviors[ i ]; m_Behaviors.Remove( i ); i--; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Alien::Spawn() { if ( asw_debug_npcs.GetBool() ) { m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT | OVERLAY_TASK_TEXT_BIT | OVERLAY_TEXT_BIT; } ChangeFaction( FACTION_ALIENS ); // get pointers to behaviors GetBehavior( &m_pFlinchBehavior ); // Precache. Precache(); SetModel( m_pszAlienModelName ); SetHullSizeNormal(); // Base spawn. BaseClass::Spawn(); // By default NPCs are always solid and cannot be stood upon. SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetBloodColor( BLOOD_COLOR_GREEN ); SetCollisionGroup( m_nAlienCollisionGroup ); SetNavType( NAV_GROUND ); SetMoveType( MOVETYPE_STEP ); // Set the initial NPC state. m_NPCState = NPC_STATE_NONE; m_vecLastPushAwayOrigin = GetAbsOrigin(); m_fFancyDeathChance = RandomFloat(); if ( m_SquadName != NULL_STRING ) { CapabilitiesAdd( bits_CAP_SQUAD ); } if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false ) { CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK ); } NPCInit(); m_flDistTooFar = 9999999.0f; LookupBurrowActivities(); //See if we're supposed to start burrowed if ( m_bStartBurrowed ) { AddEffects( EF_NODRAW ); AddEffects( EF_NOSHADOW ); AddFlag( FL_NOTARGET ); m_spawnflags |= SF_NPC_GAG; AddSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_NO; m_vecUnburrowEndPoint = GetAbsOrigin(); // set alien to fly and not collide for the duration of his unburrow AddFlag( FL_FLY ); SetGroundEntity( NULL ); SetCollisionGroup( COLLISION_GROUP_NPC_SCRIPTED ); // offset alien's position by the motion in his unburrow sequence Activity unburrowActivity = m_UnburrowActivity; int nSeq = SelectWeightedSequence( unburrowActivity ); if ( nSeq != -1 ) { Vector vecLocalDelta; GetSequenceLinearMotion( nSeq, &vecLocalDelta ); // Transform the sequence delta matrix3x4_t fRotateMatrix; AngleMatrix( GetLocalAngles(), fRotateMatrix ); Vector vecDelta; VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta ); Vector vecNewPos = GetAbsOrigin() - vecDelta; Teleport( &vecNewPos, &GetAbsAngles(), &vec3_origin ); } SetState( NPC_STATE_IDLE ); SetActivity( m_UnburrowIdleActivity ); SetSchedule( SCHED_BURROW_WAIT ); // todo: a schedule where they don't crawl out right away? } if ( m_iMoveCloneName != NULL_STRING ) { SetMoveClone( m_iMoveCloneName, NULL ); } } void CASW_Alien::Precache() { BaseClass::Precache(); PrecacheModel( m_pszAlienModelName ); //pre-cache any models used by particle gib effects int modelIndex = modelinfo->GetModelIndex( m_pszAlienModelName ); const model_t *model = modelinfo->GetModel( modelIndex ); if ( model ) { KeyValues *modelKeyValues = new KeyValues( "" ); if ( modelKeyValues->LoadFromBuffer( m_ModelName.ToCStr(), modelinfo->GetModelKeyValueText( model ) ) ) { KeyValues *pkvMeshParticleEffect = modelKeyValues->FindKey( "MeshParticles" ); if ( pkvMeshParticleEffect ) { for ( KeyValues *pSingleEffect = pkvMeshParticleEffect->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() ) { const char *pszModelName = pSingleEffect->GetString( "modelName", "" ); if( pszModelName ) { PrecacheModel( pszModelName ); } } } } modelKeyValues->deleteThis(); } PrecacheParticleSystem( "drone_death" ); // death PrecacheParticleSystem( "drone_shot" ); // shot PrecacheParticleSystem( "freeze_statue_shatter" ); } // Updates our memory about the enemies we Swarm Sensed // todo: add various swarm sense conditions? void CASW_Alien::OnSwarmSensed(int iDistance) { AISightIter_t iter; CBaseEntity *pSenseEnt; pSenseEnt = GetASWSenses()->GetFirstSwarmSenseEntity( &iter ); while( pSenseEnt ) { if ( pSenseEnt->IsPlayer() ) { // if we see a client, remember that (mostly for scripted AI) //SetCondition(COND_SEE_PLAYER); } Disposition_t relation = IRelationType( pSenseEnt ); // the looker will want to consider this entity // don't check anything else about an entity that can't be seen, or an entity that you don't care about. if ( relation != D_NU ) { if ( pSenseEnt == GetEnemy() ) { // we know this ent is visible, so if it also happens to be our enemy, store that now. //SetCondition(COND_SEE_ENEMY); } // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when // we see npcs other than the Enemy. switch ( relation ) { case D_HT: { int priority = IRelationPriority( pSenseEnt ); if (priority < 0) { //SetCondition(COND_SEE_DISLIKE); } else if (priority > 10) { //SetCondition(COND_SEE_NEMESIS); } else { //SetCondition(COND_SEE_HATE); } UpdateEnemyMemory(pSenseEnt,pSenseEnt->GetAbsOrigin()); break; } case D_FR: UpdateEnemyMemory(pSenseEnt,pSenseEnt->GetAbsOrigin()); //SetCondition(COND_SEE_FEAR); break; case D_LI: case D_NU: break; default: DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSenseEnt->GetClassname() ); break; } } pSenseEnt = GetASWSenses()->GetNextSwarmSenseEntity( &iter ); } } // create our custom senses class CAI_Senses* CASW_Alien::CreateSenses() { CAI_Senses *pSenses = new CASW_AI_Senses; pSenses->SetOuter( this ); return pSenses; } CASW_AI_Senses* CASW_Alien::GetASWSenses() { return dynamic_cast(GetSenses()); } bool CASW_Alien::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC ) { if ( !BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC ) ) return false; if ( pEntity && pEntity->Classify() == CLASS_ASW_BAIT ) { return GetAbsOrigin().DistToSqr( pEntity->GetAbsOrigin() ) < 90000.0f; // only see bait within 300 units } return true; } // wake the alien up when a marine gets nearby void CASW_Alien::UpdateSleepState(bool bInPVS) { if ( GetSleepState() > AISS_AWAKE ) // alien is asleep, check for marines getting near to wake us up { // wake up if we have a script to run if (m_hCine != NULL && GetSleepState() > AISS_AWAKE ) Wake(); bInPVS = MarineCanSee(384, 0.1f); if ( bInPVS ) SetCondition( COND_IN_PVS ); else ClearCondition( COND_IN_PVS ); if ( GetSleepState() > AISS_AWAKE && GetSleepState() != AISS_WAITING_FOR_INPUT ) { if (bInPVS) { if (asw_draw_awake_ai.GetBool()) NDebugOverlay::EntityText(entindex(), 1, "WAKING", 60, 255,255,0,255); Wake(); } } } else // alien is awake, check for going back to ZZZ again :) { bool bHasOrders = (m_AlienOrders != AOT_None); // Don't let an NPC sleep if they're running a script! if( !ShouldAlwaysThink() && !bHasOrders && !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT ) { if( m_fLastSleepCheckTime < gpGlobals->curtime + ASW_ALIEN_SLEEP_CHECK_INTERVAL ) { //if (!GetEnemy() && !MarineNearby(1024.0f) ) if (!GetEnemy() && !MarineCanSee(384, 2.0f) ) { SetSleepState( AISS_WAITING_FOR_PVS ); if (asw_draw_awake_ai.GetBool()) NDebugOverlay::EntityText(entindex(), 1, "SLEEPING", 600, 255,255,0,255); Sleep(); if (m_bVisibleWhenAsleep) RemoveEffects( EF_NODRAW ); } m_fLastSleepCheckTime = gpGlobals->curtime; } } } } void CASW_Alien::SetDistSwarmSense( float flDistSense ) { if (!GetASWSenses()) return; GetASWSenses()->SetDistSwarmSense( flDistSense ); } void CASW_Alien::NPCInit() { BaseClass::NPCInit(); // set default alien swarm sight/sense distances SetDistSwarmSense(576.0f); SetDistLook( 768.0f ); m_flDistTooFar = 1500.0f; // seems this is used as an early out for checking attack conditions or not, also for LOS? // if flagged, alien can see twice as far if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) ) { SetDistSwarmSense(1152.0f); SetDistLook( 1536.0f ); m_flDistTooFar = 2000.0f; } SetCollisionBounds( GetHullMins(), GetHullMaxs() ); CASW_GameStats.Event_AlienSpawned( this ); m_LagCompensation.Init(this); } void CASW_Alien::CallBehaviorThink() { CAI_ASW_Behavior *pCurrent = GetPrimaryASWBehavior(); if ( pCurrent ) { pCurrent->BehaviorThink(); } } void CASW_Alien::NPCThink( void ) { BaseClass::NPCThink(); if ( asw_springcol.GetBool() && CanBePushedAway() ) { PerformPushaway(); } // stop electro stunning if we're slowed if ( m_bElectroStunned && m_lifeState != LIFE_DYING ) { if ( m_fHurtSlowMoveTime < gpGlobals->curtime ) { m_bElectroStunned = false; } else { if ( gpGlobals->curtime >= m_fNextStunSound ) { m_fNextStunSound = gpGlobals->curtime + RandomFloat( 0.2f, 0.5f ); EmitSound( "ASW_Tesla_Laser.Damage" ); } } } if (gpGlobals->maxClients > 1) m_LagCompensation.StorePositionHistory(); // Update range attack if ( m_nVolleyType >= 0 ) UpdateRangedAttack(); UpdateThawRate(); m_flLastThinkTime = gpGlobals->curtime; } bool CASW_Alien::MarineNearby(float radius, bool bCheck3D) { // find the closest marine CASW_Game_Resource *pGameResource = ASWGameResource(); if (!pGameResource) return false; for (int i=0;iGetMaxMarineResources();i++) { CASW_Marine_Resource* pMarineResource = pGameResource->GetMarineResource(i); if (!pMarineResource) continue; CASW_Marine* pMarine = pMarineResource->GetMarineEntity(); if (!pMarine) continue; Vector diff = pMarine->GetAbsOrigin() - GetAbsOrigin(); float dist = bCheck3D ? diff.Length() : diff.Length2D(); if (dist < radius) { return true; } } return false; } CBaseEntity *CASW_Alien::CheckTraceHullAttack( float flDist, const Vector &mins, const Vector &maxs, float flDamage, int iDmgType, float forceScale, bool bDamageAnyNPC ) { // If only a length is given assume we want to trace in our facing direction Vector forward; AngleVectors( GetAbsAngles(), &forward ); Vector vStart = GetAbsOrigin(); // The ideal place to start the trace is in the center of the attacker's bounding box. // however, we need to make sure there's enough clearance. Some of the smaller monsters aren't // as big as the hull we try to trace with. (SJB) float flVerticalOffset = WorldAlignSize().z * 0.5; if( flVerticalOffset < maxs.z ) { // There isn't enough room to trace this hull, it's going to drag the ground. // so make the vertical offset just enough to clear the ground. flVerticalOffset = maxs.z + 1.0; } vStart.z += flVerticalOffset; Vector vEnd = vStart + (forward * flDist ); return CheckTraceHullAttack( vStart, vEnd, mins, maxs, flDamage, iDmgType, forceScale, bDamageAnyNPC ); } // alien has been hit by a melee attack void CASW_Alien::MeleeBleed(CTakeDamageInfo* info) { if ( info->GetDamage() >= 1.0 && !(info->GetDamageType() & DMG_SHOCK ) && !(info->GetDamageType() & DMG_BURN )) { Vector vecDir = -info->GetDamageForce(); vecDir.NormalizeInPlace(); UTIL_ASW_DroneBleed( info->GetDamagePosition() + m_LagCompensation.GetLagCompensationOffset(), vecDir, 4 ); } } //----------------------------------------------------------------------------- // Freezes this NPC in place for a period of time. //----------------------------------------------------------------------------- void CASW_Alien::Freeze( float flFreezeAmount, CBaseEntity *pFreezer, Ray_t *pFreezeRay ) { if ( flFreezeAmount <= 0.0f ) { SetCondition(COND_NPC_FREEZE); SetMoveType(MOVETYPE_NONE); SetGravity(0); SetLocalAngularVelocity(vec3_angle); SetAbsVelocity( vec3_origin ); return; } if ( flFreezeAmount > 1.0f ) { float flFreezeDuration = flFreezeAmount - 1.0f; // if freezing permanently, then reduce freeze duration by freeze resistance flFreezeDuration *= ( 1.0f - m_flFreezeResistance ); BaseClass::Freeze( 1.0f, pFreezer, pFreezeRay ); // make alien fully frozen m_flFrozenTime = gpGlobals->curtime + flFreezeDuration; } else { // if doing a partial freeze, then freeze resistance reduces that flFreezeAmount *= ( 1.0f - m_flFreezeResistance ); BaseClass::Freeze( flFreezeAmount, pFreezer, pFreezeRay ); } UpdateThawRate(); } bool CASW_Alien::ShouldBecomeStatue() { // Prevents parent classes from turning this NPC into a statue // We handle that ourselves if the alien dies while iced up return false; } void CASW_Alien::UpdateThawRate() { if ( m_flFrozenTime > gpGlobals->curtime ) { m_flFrozenThawRate = 0.0f; } else { m_flFrozenThawRate = m_flBaseThawRate * (1.5f - m_flFrozen); } } //------------------------------------------------------------------------------ // Purpose : start and end trace position, amount // of damage to do, and damage type. Returns a pointer to // the damaged entity in case the NPC wishes to do // other stuff to the victim (punchangle, etc) // // Used for many contact-range melee attacks. Bites, claws, etc. // Input : // Output : //------------------------------------------------------------------------------ CBaseEntity *CASW_Alien::CheckTraceHullAttack( const Vector &vStart, const Vector &vEnd, const Vector &mins, const Vector &maxs, float flDamage, int iDmgType, float flForceScale, bool bDamageAnyNPC ) { // Handy debuging tool to visualize HullAttack trace if ( ai_show_hull_attacks.GetBool() ) { float length = (vEnd - vStart ).Length(); Vector direction = (vEnd - vStart ); VectorNormalize( direction ); Vector hullMaxs = maxs; hullMaxs.x = length + hullMaxs.x; NDebugOverlay::BoxDirection(vStart, mins, hullMaxs, direction, 100,255,255,20,1.0); NDebugOverlay::BoxDirection(vStart, mins, maxs, direction, 255,0,0,20,1.0); } CTakeDamageInfo dmgInfo( this, this, flDamage, DMG_SLASH ); CASW_Trace_Filter_Melee traceFilter( this, COLLISION_GROUP_NONE, this, bDamageAnyNPC ); Ray_t ray; ray.Init( vStart, vEnd, mins, maxs ); trace_t tr; enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr ); Vector vecAttackerCenter = WorldSpaceCenter(); for ( int i = 0; i < traceFilter.m_nNumHits; i++ ) { trace_t *tr = &traceFilter.m_HitTraces[i]; CBaseEntity *pHitEntity = tr->m_pEnt; Vector attackDir = pHitEntity->WorldSpaceCenter() - vecAttackerCenter; VectorNormalize( attackDir ); CalculateMeleeDamageForce( &dmgInfo, attackDir, vecAttackerCenter, flForceScale ); tr->m_pEnt->DispatchTraceAttack( dmgInfo, tr->endpos - vecAttackerCenter, tr ); #ifdef GAME_DLL ApplyMultiDamage(); #endif } CBaseEntity *pEntity = traceFilter.m_pHit; return pEntity; } //----------------------------------------------------------------------------- // Purpose: // NOTE: I removed the ON_GROUND check for Alien Swarm. Do we need to put it // back? //----------------------------------------------------------------------------- int CASW_Alien::MeleeAttack1Conditions( float flDot, float flDist ) { // Should we even check this condition? if ( !meleeAttack1.m_bCheck ) return COND_NONE; // Check range. if ( flDist < meleeAttack1.m_flMinDist ) return COND_TOO_CLOSE_TO_ATTACK; if ( flDist > meleeAttack1.m_flMaxDist ) return COND_TOO_FAR_TO_ATTACK; // Check facing. if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK ) { if ( flDot < meleeAttack1.m_flDotAngle ) return COND_NOT_FACING_ATTACK; } return COND_CAN_MELEE_ATTACK1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CASW_Alien::MeleeAttack2Conditions( float flDot, float flDist ) { // Should we even check this condition? if ( !meleeAttack2.m_bCheck ) return COND_NONE; // Check ranges. if ( flDist < meleeAttack2.m_flMinDist ) return COND_TOO_CLOSE_TO_ATTACK; if ( flDist > meleeAttack2.m_flMaxDist ) return COND_TOO_FAR_TO_ATTACK; // Check facing. if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK ) { if ( flDot < meleeAttack2.m_flDotAngle ) return COND_NOT_FACING_ATTACK; } return COND_CAN_MELEE_ATTACK2; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CASW_Alien::RangeAttack1Conditions ( float flDot, float flDist ) { // Should we even check this condition? if ( !rangeAttack1.m_bCheck ) return COND_NONE; // Check ranges. if ( flDist < rangeAttack1.m_flMinDist ) return COND_TOO_CLOSE_TO_ATTACK; if ( flDist > rangeAttack1.m_flMaxDist ) return COND_TOO_FAR_TO_ATTACK; // Check facing. if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK ) { if ( flDot < rangeAttack1.m_flDotAngle ) return COND_NOT_FACING_ATTACK; } return COND_CAN_RANGE_ATTACK1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CASW_Alien::RangeAttack2Conditions ( float flDot, float flDist ) { // Should we even check this condition? if ( !rangeAttack2.m_bCheck ) return COND_NONE; // Check ranges. if ( flDist < rangeAttack2.m_flMinDist ) return COND_TOO_CLOSE_TO_ATTACK; if ( flDist > rangeAttack2.m_flMaxDist ) return COND_TOO_FAR_TO_ATTACK; // Check facing. if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK ) { if ( flDot < rangeAttack2.m_flDotAngle ) return COND_NOT_FACING_ATTACK; } return COND_CAN_RANGE_ATTACK2; } // give our aliens 360 degree vision bool CASW_Alien::FInViewCone( const Vector &vecSpot ) { return true; } // always gib bool CASW_Alien::ShouldGib( const CTakeDamageInfo &info ) { return true; } // player (and player controlled marines) always avoid drones bool CASW_Alien::ShouldPlayerAvoid( void ) { return true; } // catching on fire int CASW_Alien::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { int result = 0; // scale burning damage up //if (dynamic_cast(info.GetAttacker())) if (dynamic_cast(info.GetInflictor())) { CTakeDamageInfo newDamage = info; newDamage.ScaleDamage(asw_fire_alien_damage_scale.GetFloat()); if (asw_debug_alien_damage.GetBool()) { Msg("%d %s hurt by %f dmg (scaled up by asw_fire_alien_damage_scale)\n", entindex(), GetClassname(), newDamage.GetDamage()); } result = BaseClass::OnTakeDamage_Alive(newDamage); } else { if (asw_debug_alien_damage.GetBool()) { Msg("%d %s hurt by %f dmg\n", entindex(), GetClassname(), info.GetDamage()); } result = BaseClass::OnTakeDamage_Alive(info); } // if we take fire damage, catch on fire if ( result > 0 && ( info.GetDamageType() & DMG_BURN ) ) { ASW_Ignite( asw_alien_burn_duration.GetFloat(), 0, info.GetAttacker(), info.GetWeapon() ); } // make the alien move slower for 0.5 seconds if (info.GetDamageType() & DMG_SHOCK) { ElectroStun( asw_stun_grenade_time.GetFloat() ); m_fNoDamageDecal = true; } else { if (m_fHurtSlowMoveTime < gpGlobals->curtime + 0.5f) m_fHurtSlowMoveTime = gpGlobals->curtime + 0.5f; } CASW_Marine* pMarine = NULL; if ( info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE ) { pMarine = static_cast( info.GetAttacker() ); } if (pMarine) pMarine->HurtAlien(this, info); // Notify gamestats of the damage CASW_GameStats.Event_AlienTookDamage( this, info ); if ( m_RecentDamage.Count() == ASW_NUM_RECENT_DAMAGE ) { m_RecentDamage.RemoveAtHead(); } m_RecentDamage.Insert( info ); if ( m_pFlinchBehavior ) { m_pFlinchBehavior->OnOuterTakeDamage( info ); } return result; } // =================== // schedule/task stuff // ==================== void CASW_Alien::StartTask( const Task_t *pTask ) { //int task = pTask->iTask; switch ( pTask->iTask ) { case TASK_BURROW_WAIT: if ( pTask->flTaskData == 1.0f ) { //Set our next burrow time if (m_flBurrowTime == 0) m_flBurrowTime = gpGlobals->curtime; else m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 1, 6); } break; case TASK_UNBURROW: Unburrow(); break; case TASK_CHECK_FOR_UNBORROW: //if ( ValidBurrowPoint( GetAbsOrigin() ) ) { m_spawnflags &= ~SF_NPC_GAG; RemoveSolidFlags( FSOLID_NOT_SOLID ); TaskComplete(); } break; case TASK_SET_UNBURROW_IDLE_ACTIVITY: { SetIdealActivity( m_UnburrowIdleActivity ); } break; case TASK_ASW_WAIT_FOR_ORDER_MOVE: { if ( IsMovementFrozen() ) { TaskFail(FAIL_FROZEN); break; } if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) { TaskComplete(); GetNavigator()->ClearGoal(); // Clear residual state } else if (!GetNavigator()->IsGoalActive()) { SetIdealActivity( GetStoppedActivity() ); } else { // Check validity of goal type ValidateNavGoal(); } } break; case TASK_ASW_ORDER_RETRY_WAIT: //Msg("TASK_ASW_ORDER_RETRY_WAIT %d tries. doorblocked=%d\n", m_iNumASWOrderRetries, m_bBlockedByOpeningDoor); m_iNumASWOrderRetries++; if (m_iNumASWOrderRetries > 5) { TaskFail("Failed to find route to order target\n"); } else { SetWait(MIN(float(m_iNumASWOrderRetries) / 2.0f, 12.0f)); // wait a bit longer in between each try } break; case TASK_ASW_REPORT_BLOCKING_ENT: Msg("Last blocking ent was %d:%s\n", GetNavigator()->GetBlockingEntity(), GetNavigator()->GetBlockingEntity() ? GetNavigator()->GetBlockingEntity()->GetClassname() : "Unknown"); TaskComplete(); break; case TASK_ASW_ALIEN_ZIGZAG: m_bPerformingZigZag = false; break; // override the default TASK_GET_PATH_TO_ENEMY to make our alien run at the end of the path case TASK_GET_PATH_TO_ENEMY: { if (IsUnreachable(GetEnemy())) { TaskFail(FAIL_NO_ROUTE); return; } CBaseEntity *pEnemy = GetEnemy(); if ( pEnemy == NULL ) { TaskFail(FAIL_NO_ENEMY); return; } if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) ) { if (m_bRunAtChasingPathEnds) { //Msg("Setting arrival speed to %f\n", GetIdealSpeed()); //GetNavigator()->SetArrivalSpeed(GetIdealSpeed()); // asw test GetNavigator()->SetArrivalSpeed(300.0f); // asw test } TaskComplete(); } else { // no way to get there =( DevWarning( 2, "GetPathToEnemy failed!!\n" ); RememberUnreachable(GetEnemy()); TaskFail(FAIL_NO_ROUTE); } return; } case TASK_GET_PATH_TO_ENEMY_LKP: { CBaseEntity *pEnemy = GetEnemy(); if (!pEnemy || IsUnreachable(pEnemy)) { TaskFail(FAIL_NO_ROUTE); return; } AI_NavGoal_t goal( GetEnemyLKP() ); TranslateNavGoal( pEnemy, goal.dest ); if ( GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ) ) { if (m_bRunAtChasingPathEnds) { //Msg("Setting arrival speed to %f\n", GetIdealSpeed()); //GetNavigator()->SetArrivalSpeed(GetIdealSpeed()); // asw test GetNavigator()->SetArrivalSpeed(300.0f); // asw test } TaskComplete(); } else { // no way to get there =( DevWarning( 2, "GetPathToEnemyLKP failed!!\n" ); RememberUnreachable(GetEnemy()); TaskFail(FAIL_NO_ROUTE); } break; } case TASK_ASW_SPREAD_THEN_HIBERNATE: { // This task really uses 2 parameters, so we have to extract // them from a single integer. To send both parameters, the // formula is MIN_DIST * 10000 + MAX_DIST { int iMinDist, iMaxDist; iMinDist = 90; iMaxDist = 200; // try to find a simple vector goal if ( GetNavigator()->SetWanderGoal( iMinDist, iMaxDist ) ) TaskComplete(); else { // if we couldn't go for a full path if ( GetNavigator()->SetRandomGoal( 150.0f ) ) TaskComplete(); else TaskFail(FAIL_NO_REACHABLE_NODE); } } } break; case TASK_ASW_BUILD_PATH_TO_ORDER: { CBaseEntity *pGoalEnt = m_AlienOrderObject; m_bFailedMoveTo = false; if (pGoalEnt) { // if our target is a path corner, do normal AI following of that route bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY); if (pGoalEnt->ClassMatches("path_corner")) { SetGoalEnt(pGoalEnt); AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pGoalEnt->GetAbsOrigin(), bIsFlying ? ACT_FLY : ACT_WALK, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST); SetState( NPC_STATE_IDLE ); if ( !GetNavigator()->SetGoal( goal ) ) { DevWarning( 2, "Can't Create Route!\n" ); m_bFailedMoveTo = true; } else { TaskComplete(); } } else { // HACKHACK: Call through TranslateNavGoal to fixup this goal position // UNDONE: Remove this and have NPCs that need this functionality fix up paths in the // movement system instead of when they are specified. AI_NavGoal_t goal(pGoalEnt->GetAbsOrigin(), bIsFlying ? ACT_FLY : ACT_WALK, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST); TranslateNavGoal( pGoalEnt, goal.dest ); if (!GetNavigator()->SetGoal( goal )) { DevWarning( 2, "Can't Create Route!\n" ); m_bFailedMoveTo = true; } else { TaskComplete(); } } } if (!pGoalEnt || m_bFailedMoveTo) { //m_AlienOrders = AOT_SpreadThenHibernate; // make sure our orders are set correctly for this action (may be incorrect if a MoveTo order fails) if (pTask->flTaskData <= 0) // check the taskdata to see if we should do a short spread movement when failing to build a path { // random nearby position if ( GetNavigator()->SetWanderGoal( 90, 200 ) ) { TaskComplete(); } else { // if we couldn't go for a full path if ( GetNavigator()->SetRandomGoal( 150.0f ) ) { TaskComplete(); } TaskFail(FAIL_NO_ROUTE); } } else { TaskFail(FAIL_NO_ROUTE); } } } break; default: { BaseClass::StartTask(pTask); break; } } } bool CASW_Alien::IsCurTaskContinuousMove() { const Task_t* pTask = GetTask(); if( !pTask || pTask->iTask == TASK_ASW_WAIT_FOR_ORDER_MOVE ) return true; return BaseClass::IsCurTaskContinuousMove(); } void CASW_Alien::RunTask(const Task_t *pTask) { switch (pTask->iTask) { case TASK_BURROW_WAIT: //See if enough time has passed if ( m_flBurrowTime < gpGlobals->curtime ) { TaskComplete(); } break; case TASK_ASW_WAIT_FOR_ORDER_MOVE: { if ( IsMovementFrozen() ) { TaskFail(FAIL_FROZEN); break; } bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() ); if (fTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE) { TaskComplete(); GetNavigator()->StopMoving(); // Stop moving } else if (!GetNavigator()->IsGoalActive()) { SetIdealActivity( GetStoppedActivity() ); } else { // Check validity of goal type ValidateNavGoal(); if ( m_AlienOrderObject.Get() ) { const Vector &vecGoalPos = m_AlienOrderObject->GetAbsOrigin(); if ( ( GetNavigator()->GetGoalPos() - vecGoalPos ).LengthSqr() > Square( 60 ) ) { if ( GetNavigator()->GetNavType() != NAV_JUMP ) { if ( !GetNavigator()->UpdateGoalPos( vecGoalPos ) ) { TaskFail( FAIL_NO_ROUTE ); } } } } } break; } case TASK_CHECK_FOR_UNBORROW: //Must wait for our next check time if ( m_flBurrowTime > gpGlobals->curtime ) return; //See if we can pop up // todo: see if a marine is near? //if ( ValidBurrowPoint( GetAbsOrigin() ) ) { m_spawnflags &= ~SF_NPC_GAG; RemoveSolidFlags( FSOLID_NOT_SOLID ); TaskComplete(); return; } //Try again in a couple of seconds m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f ); break; case TASK_UNBURROW: { AutoMovement(); if ( IsActivityFinished() ) { RemoveFlag( FL_FLY ); CheckForBlockingTraps(); SetCollisionGroup( m_nAlienCollisionGroup ); TaskComplete(); } break; } case TASK_SET_UNBURROW_IDLE_ACTIVITY: { AutoMovement(); if ( IsActivityFinished() ) { TaskComplete(); } break; } case TASK_ASW_ORDER_RETRY_WAIT: { if ( IsWaitFinished() ) { TaskComplete(); } break; } case TASK_ASW_ALIEN_ZIGZAG: { if ( IsMovementFrozen() ) { TaskFail(FAIL_FROZEN); break; } if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) { m_bPerformingZigZag = false; TaskComplete(); GetNavigator()->StopMoving(); // Stop moving } else if (!GetNavigator()->IsGoalActive()) { SetIdealActivity( GetStoppedActivity() ); } else if (ValidateNavGoal()) { SetIdealActivity( GetNavigator()->GetMovementActivity() ); // if we've just spotted our enemy, recompute our path, to try and get a single local path to add a zigzag to /* if (HasCondition( COND_SEE_ENEMY ) && !m_bLastSeeEnemy) { if ( !GetNavigator()->RefindPathToGoal( false ) ) { TaskFail( FAIL_NO_ROUTE ); return; } } m_bLastSeeEnemy = HasCondition( COND_SEE_ENEMY ); if (GetNavigator()->CurWaypointIsGoal()) { m_bPerformingZigZag = false; AddZigZagToPath(); }*/ } break; } default: { BaseClass::RunTask( pTask ); } } } #define ZIG_ZAG_SIZE 1500 // was 3600 // don't change our chase goal while we're on a zigzag detour bool CASW_Alien::ShouldUpdateEnemyPos() { if (GetTask() && GetTask()->iTask == TASK_ASW_ALIEN_ZIGZAG //&& (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR) ) && m_bPerformingZigZag) return false; // don't repath if we're heading to a point that shouldn't be simplified if (asw_drone_zig_zagging.GetBool() && GetNavigator()->GetCurWaypointFlags() & bits_WP_DONT_SIMPLIFY) { return false; } //Msg("updating enemy pos %f\n", gpGlobals->curtime); return true; } float CASW_Alien::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos ) { if (!ShouldUpdateEnemyPos()) return 10000.0f; // allow huge tolerance to prevent repath //return BaseClass::GetGoalRepathTolerance(pGoalEnt, type, curGoal, curTargetPos); float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance(); float distMoved1Sec = GetSmoothedVelocity().Length(); float result = 120; // FIXME: why 120? if (distToGoal <= 100) return GetMotor()->MinStoppingDist(0.0f); if (distMoved1Sec > 0.0) { float t = distToGoal / distMoved1Sec; result = clamp( 120 * t, 0, 120 ); // Msg("t %.2f : d %.0f (%.0f)\n", t, result, distMoved1Sec ); } if ( !pGoalEnt->IsPlayer() ) result *= 1.20; return result; } void CASW_Alien::AddZigZagToPath(void) { // If already on a detour don't add a zigzag if (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR) { return; } // If enemy isn't facing me or occluded, don't add a zigzag //if (HasCondition(COND_ENEMY_OCCLUDED) || !HasCondition ( COND_ENEMY_FACING_ME )) //{ //return; //} Vector waypointPos = GetNavigator()->GetCurWaypointPos(); Vector waypointDir = (waypointPos - GetAbsOrigin()); // If the distance to the next node is greater than ZIG_ZAG_SIZE // then add a random zig/zag to the path if (waypointDir.LengthSqr() > ZIG_ZAG_SIZE) { // Pick a random distance for the zigzag (less that sqrt(ZIG_ZAG_SIZE) float fZigZigDistance = random->RandomFloat( 100, 200 ); //30, 60 ); int iZigZagSide = random->RandomInt(0,1); Msg("adding a zig zag of %f units\n", fZigZigDistance); // Get me a vector orthogonal to the direction of motion VectorNormalize( waypointDir ); Vector vDirUp(0,0,1); Vector vDir; CrossProduct( waypointDir, vDirUp, vDir); // Pick a random direction (left/right) for the zigzag if (iZigZagSide) { vDir = -1 * vDir; } // Get zigzag position in direction of target waypoint Vector zigZagPos = GetAbsOrigin() + waypointDir * 200; // Now offset zigZagPos = zigZagPos + (vDir * fZigZigDistance); // Now make sure that we can still get to the zigzag position and the waypoint AIMoveTrace_t moveTrace1, moveTrace2; GetMoveProbe()->MoveLimit( NAV_GROUND, GetAbsOrigin(), zigZagPos, MASK_NPCSOLID, NULL, &moveTrace1); GetMoveProbe()->MoveLimit( NAV_GROUND, zigZagPos, waypointPos, MASK_NPCSOLID, NULL, &moveTrace2); if ( !IsMoveBlocked( moveTrace1 ) && !IsMoveBlocked( moveTrace2 ) ) { m_bPerformingZigZag = true; GetNavigator()->PrependWaypoint( zigZagPos, NAV_GROUND, bits_WP_TO_DETOUR ); } } } int CASW_Alien::TranslateSchedule( int scheduleType ) { if (scheduleType == SCHED_CHASE_ENEMY) return SCHED_ASW_ALIEN_CHASE_ENEMY; int i = BaseClass::TranslateSchedule(scheduleType); if ( i == SCHED_MELEE_ATTACK1 ) { if ( ShouldStopBeforeMeleeAttack() ) { return SCHED_ASW_ALIEN_SLOW_MELEE_ATTACK1; } return SCHED_ASW_ALIEN_MELEE_ATTACK1; } return i; } // pushes aliens out of each other void CASW_Alien::PerformPushaway() { VPROF("CASW_Alien::PerformPushaway"); SetupPushawayVector(); #ifdef MOVEPROBE_PUSHAWAY AIMoveTrace_t moveTrace; unsigned testFlags = AITGM_IGNORE_FLOOR; GetMoveProbe()->TestGroundMove( GetLocalOrigin(), GetAbsOrigin() + m_vecLastPush, GetAITraceMask(), testFlags, &moveTrace ); UTIL_SetOrigin( this, moveTrace.vEndPosition, true ); #else // check if we can safely move through this push Vector dest = GetAbsOrigin() + m_vecLastPush; trace_t tr; AI_TraceHull( GetAbsOrigin(), dest, WorldAlignMins(), WorldAlignMaxs(), GetAITraceMask(), this, GetCollisionGroup(), &tr ); if ( !tr.startsolid && (tr.fraction == 1.0) ) { // all was clear, move into new position UTIL_SetOrigin(this, dest); } #endif // if we're close to stopping, zero our velocity if ( GetMotor()->GetCurSpeed() < 1.0f ) { GetMotor()->SetMoveVel( Vector( 0,0,0 ) ); } // if we were pushed, slow us down a bit else if ( m_bPushed ) { float scale_vel = 1.0f - ( m_vecLastPush.Length2D() / asw_springcol_push_cap.GetFloat() ); GetMotor()->SetMoveVel( GetMotor()->GetCurVel() * scale_vel ); } } float CASW_Alien::GetSpringColRadius() { return asw_springcol_radius.GetFloat(); } void CASW_Alien::SetupPushawayVector() { CASW_Alien *pOtherAlien; Vector vecPush; vecPush.Init(); bool m_bPushed = false; float flSpringColRadius = GetSpringColRadius(); // go through all aliens int iAliens = IAlienAutoList::AutoList().Count(); for ( int i = 0; i < iAliens; i++ ) { pOtherAlien = static_cast< CASW_Alien* >( IAlienAutoList::AutoList()[ i ] ); if ( pOtherAlien != this && !( Classify() == CLASS_ASW_HARVESTER && pOtherAlien->Classify() != CLASS_ASW_PARASITE ) && !( Classify() == CLASS_ASW_PARASITE && pOtherAlien->Classify() != CLASS_ASW_HARVESTER ) ) { Vector diff = m_vecLastPushAwayOrigin - pOtherAlien->GetAbsOrigin(); float dist = diff.Length(); VectorNormalize( diff ); // TODO: Customize springcolradius per alien type if ( dist < flSpringColRadius ) // if alien is too close, add a force from him to our push vector { dist -= flSpringColRadius * asw_springcol_core.GetFloat(); if (dist < 1) dist = 1; float push_amount = flSpringColRadius / dist; vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat(); m_bPushed = true; } } } // cap the push vector float over_length = vecPush.Length() / asw_springcol_push_cap.GetFloat(); if (over_length > 1) vecPush /= over_length; // push out of players with equal force to total push from other drones int iMaxMarines = ASWGameResource()->GetMaxMarineResources(); for ( int i = 0; i < iMaxMarines; i++ ) { CASW_Marine_Resource *pMR = ASWGameResource()->GetMarineResource( i ); if ( !pMR ) continue; CASW_Marine *pMarine = pMR->GetMarineEntity(); if ( !pMarine ) continue; Vector diff = m_vecLastPushAwayOrigin - pMarine->GetAbsOrigin(); float dist = diff.Length(); VectorNormalize( diff ); if ( dist < flSpringColRadius ) // if drone is too close, add a force from him to our push vector { dist -= flSpringColRadius * asw_springcol_core.GetFloat(); if ( dist < 1 ) { dist = 1; } float push_amount = flSpringColRadius / dist; vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat(); m_bPushed = true; } } // push away from tesla traps int nTraps = g_aTeslaTraps.Count(); for ( int i = 0; i < nTraps; i++ ) { Vector diff = m_vecLastPushAwayOrigin - g_aTeslaTraps[i]->GetAbsOrigin(); float dist = diff.Length(); VectorNormalize( diff ); if ( dist < flSpringColRadius ) // if drone is too close, add a force from him to our push vector { dist -= flSpringColRadius * asw_springcol_core.GetFloat(); if ( dist < 1 ) { dist = 1; } float push_amount = flSpringColRadius / dist; vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat(); m_bPushed = true; } } // cap the push vector over_length = vecPush.Length() / asw_springcol_push_cap.GetFloat(); if ( over_length > 1 ) { vecPush /= over_length; } // smooth the push vector m_vecLastPush = (m_vecLastPush * 2.0f + vecPush) / 3.0f; //Msg("%d Pushaway vector size %f\n", entindex(), m_vecLastPush.Length2D()); if ( asw_springcol_debug.GetInt() == -1 || asw_springcol_debug.GetInt() == entindex() ) { float flYaw = UTIL_VecToYaw( m_vecLastPush ); NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), flYaw, 64, 8, 255, 255, 0, 0, true, 0.1f ); } m_vecLastPushAwayOrigin = GetAbsOrigin(); } bool CASW_Alien::CanBePushedAway() { // no pushing away while burrowed if ( IsCurSchedule( SCHED_BURROW_WAIT, false ) || IsCurSchedule( SCHED_WAIT_FOR_CLEAR_UNBORROW, false ) || IsCurSchedule( SCHED_BURROW_OUT, false ) ) return false; // don't push away aliens that have low sight ('asleep') return !IsCurSchedule( CAI_ASW_SleepBehavior::SCHED_SLEEP_UNBURROW ); } AI_BEGIN_CUSTOM_NPC( asw_alien, CASW_Alien ) DECLARE_CONDITION( COND_ASW_BEGIN_COMBAT_STUN ) DECLARE_CONDITION( COND_ASW_FLINCH ) DECLARE_TASK( TASK_ASW_ALIEN_ZIGZAG ) DECLARE_TASK( TASK_ASW_SPREAD_THEN_HIBERNATE ) DECLARE_TASK( TASK_ASW_BUILD_PATH_TO_ORDER ) DECLARE_TASK( TASK_ASW_ORDER_RETRY_WAIT ) DECLARE_TASK( TASK_ASW_REPORT_BLOCKING_ENT ) DECLARE_TASK( TASK_UNBURROW ) DECLARE_TASK( TASK_CHECK_FOR_UNBORROW ) DECLARE_TASK( TASK_BURROW_WAIT ) DECLARE_TASK( TASK_SET_UNBURROW_IDLE_ACTIVITY ) DECLARE_TASK( TASK_ASW_WAIT_FOR_ORDER_MOVE ) DECLARE_ACTIVITY( ACT_MELEE_ATTACK1_HIT ) DECLARE_ACTIVITY( ACT_MELEE_ATTACK2_HIT ) DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_SMALL ) DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_MEDIUM ) DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_BIG ) DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_GESTURE ) DECLARE_ACTIVITY( ACT_DEATH_FIRE ) DECLARE_ACTIVITY( ACT_DEATH_ELEC ) DECLARE_ACTIVITY( ACT_DIE_FANCY ) DECLARE_ACTIVITY( ACT_BURROW_OUT ) DECLARE_ACTIVITY( ACT_BURROW_IDLE ) DECLARE_ANIMEVENT( AE_ALIEN_MELEE_HIT ) //========================================================= // > SCHED_ASW_ALIEN_CHASE_ENEMY //========================================================= DEFINE_SCHEDULE ( SCHED_ASW_ALIEN_CHASE_ENEMY, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" //" TASK_SET_TOLERANCE_DISTANCE 24" " TASK_GET_PATH_TO_ENEMY 300" " TASK_RUN_PATH 0" //" TASK_ASW_ALIEN_ZIGZAG 0" " TASK_WAIT_FOR_MOVEMENT 0" "" " Interrupts" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_CAN_RANGE_ATTACK1" " COND_CAN_MELEE_ATTACK1" " COND_CAN_RANGE_ATTACK2" " COND_CAN_MELEE_ATTACK2" " COND_TOO_CLOSE_TO_ATTACK" " COND_TASK_FAILED" " COND_HEAR_DANGER" ) // Same as base melee attack1, minus the TASK_STOP_MOVING DEFINE_SCHEDULE ( SCHED_ASW_ALIEN_MELEE_ATTACK1, " Tasks" " TASK_FACE_ENEMY 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_ALIEN_SLOW_MELEE_ATTACK1, " Tasks" " TASK_STOP_MOVING 1" " TASK_WAIT 0.1" " TASK_FACE_ENEMY 0" " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack " TASK_MELEE_ATTACK1 1" "" " Interrupts" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" " COND_ENEMY_OCCLUDED" ); DEFINE_SCHEDULE ( SCHED_ASW_SPREAD_THEN_HIBERNATE, " Tasks" " TASK_STOP_MOVING 0" " TASK_ASW_SPREAD_THEN_HIBERNATE 0" " TASK_WALK_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_STOP_MOVING 0" " TASK_SET_SCHEDULE SCHEDULE:SCHED_IDLE_STAND" " " " Interrupts" " COND_NEW_ENEMY" " COND_SEE_ENEMY" " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" ) DEFINE_SCHEDULE ( SCHED_ASW_ORDER_MOVE, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_RETRY_ORDERS" " TASK_ASW_BUILD_PATH_TO_ORDER 0" " TASK_RUN_PATH 0" " TASK_ASW_WAIT_FOR_ORDER_MOVE 0" // 0 is spread if fail to build path //" TASK_WAIT_PVS 0" "" " Interrupts" " COND_NEW_ENEMY" " COND_SEE_ENEMY" // in deference to scripted schedule where the enemy was slammed, thus no COND_NEW_ENEMY " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" " COND_SMELL" " COND_PROVOKED" " COND_HEAR_COMBAT" " COND_HEAR_BULLET_IMPACT" ) DEFINE_SCHEDULE ( SCHED_ASW_RETRY_ORDERS, " Tasks" //" TASK_ASW_REPORT_BLOCKING_ENT 0" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_SPREAD_THEN_HIBERNATE" " TASK_ASW_ORDER_RETRY_WAIT 1" // this will fail the schedule if we've retried too many times " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_RETRY_ORDERS" " TASK_ASW_BUILD_PATH_TO_ORDER 1" // 1 is no spread if fail to build path " TASK_WALK_PATH 9999" " TASK_WAIT_FOR_MOVEMENT 0" // 0 is spread if fail to build path " TASK_WAIT_PVS 0" "" " Interrupts" " COND_NEW_ENEMY" " COND_SEE_ENEMY" // in deference to scripted schedule where the enemy was slammed, thus no COND_NEW_ENEMY " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" " COND_SMELL" " COND_PROVOKED" " COND_HEAR_COMBAT" " COND_HEAR_BULLET_IMPACT" ) DEFINE_SCHEDULE ( SCHED_BURROW_WAIT, " Tasks" " TASK_SET_UNBURROW_IDLE_ACTIVITY 0" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BURROW_WAIT" " TASK_BURROW_WAIT 1" " TASK_SET_SCHEDULE SCHEDULE:SCHED_WAIT_FOR_CLEAR_UNBORROW" "" " Interrupts" " COND_TASK_FAILED" ) DEFINE_SCHEDULE ( SCHED_WAIT_FOR_CLEAR_UNBORROW, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BURROW_WAIT" " TASK_CHECK_FOR_UNBORROW 1" " TASK_SET_SCHEDULE SCHEDULE:SCHED_BURROW_OUT" "" " Interrupts" " COND_TASK_FAILED" ) DEFINE_SCHEDULE ( SCHED_BURROW_OUT, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BURROW_WAIT" " TASK_UNBURROW 0" "" " Interrupts" " COND_TASK_FAILED" ) AI_END_CUSTOM_NPC() // Behaviour stuff to support act busy bool CASW_Alien::CreateBehaviors() { AddBehavior( &m_ActBusyBehavior ); return BaseClass::CreateBehaviors(); } int CASW_Alien::SelectAlienOrdersSchedule() { int schedule = 0; // check if we have any alien orders to follow switch (m_AlienOrders) { case AOT_MoveToIgnoringMarines: { IgnoreMarines(true); schedule = SCHED_ASW_ORDER_MOVE; } break; case AOT_SpreadThenHibernate: case AOT_MoveTo: { IgnoreMarines(false); schedule = SCHED_ASW_ORDER_MOVE; } break; case AOT_MoveToNearestMarine: { float marine_distance; // refresh our order target, in case the one passed in by the orders is no longer the nearest m_AlienOrderObject = UTIL_ASW_NearestMarine( GetAbsOrigin(), marine_distance ); IgnoreMarines(false); schedule = SCHED_ASW_ORDER_MOVE; } break; case AOT_None: default: { // nothing } } return schedule; } int CASW_Alien::SelectSchedule( void ) { // If we're supposed to be burrowed, stay there if ( m_bStartBurrowed ) return SCHED_BURROW_WAIT; // see if our alien orders want to schedule something int order_schedule = SelectAlienOrdersSchedule(); if (order_schedule > 0) { //Msg("SelectSchedule picked alien orders\n"); return order_schedule; } if ( !BehaviorSelectSchedule() ) { } return BaseClass::SelectSchedule(); } ConVar asw_drone_death_force_pitch( "asw_drone_death_force_pitch", "-10", FCVAR_CHEAT, "Tilt the angle of death forces on alien ragdolls" ); ConVar asw_drone_death_force( "asw_drone_death_force", "5", FCVAR_CHEAT, "Scale for alien death forces" ); #define DRONE_MASS 90.0f Vector CASW_Alien::CalcDeathForceVector( const CTakeDamageInfo &info ) { if ( asw_debug_alien_damage.GetBool() ) { Msg( "Death force pre = %f\n", info.GetDamageForce().Length() ); } if ( !( info.GetDamageType() & DMG_BLAST || info.GetDamageType() & DMG_BURN ) ) { // normalize force for non-explosive weapons, as they each have different fire rates/forces // TODO: Tilt force vector upwards a bit float flDesiredForceScale = asw_drone_death_force.GetFloat() * 10000.0f; float flMassScale = 1.0f; if ( VPhysicsGetObject() ) { flMassScale = 1.0f / ( VPhysicsGetObject()->GetMass() / DRONE_MASS ); // using drone's mass as a baseline } CBaseEntity *pForce = info.GetInflictor(); if ( !pForce ) { pForce = info.GetAttacker(); } Vector forceVector = vec3_origin; if ( pForce ) { forceVector = GetAbsOrigin() - pForce->GetAbsOrigin(); forceVector.NormalizeInPlace(); if ( asw_debug_alien_damage.GetBool() ) { Msg( "Death force post = %f flDesiredForceScale=%f flMassScale=%f\n", (forceVector * flDesiredForceScale * flMassScale).Length(), flDesiredForceScale, flMassScale ); } return forceVector * flDesiredForceScale * flMassScale; } else { forceVector.x = random->RandomFloat( -1.0f, 1.0f ); forceVector.y = random->RandomFloat( -1.0f, 1.0f ); forceVector.z = 0.0; if ( asw_debug_alien_damage.GetBool() ) { Msg( "Death force post = %f flDesiredForceScale=%f flMassScale=%f\n", (forceVector * flDesiredForceScale * flMassScale).Length(), flDesiredForceScale, flMassScale ); } return forceVector * (flDesiredForceScale/4) * flMassScale; } } return BaseClass::CalcDeathForceVector( info ); } void CASW_Alien::BreakAlien( const CTakeDamageInfo &info ) { Vector velocity; velocity = CalcDeathForceVector( info )/150; if ( velocity == vec3_origin ) velocity = Vector( RandomFloat( 1, 15 ), RandomFloat( 1, 15 ), 75.0f ); velocity.z *= 1.25; velocity.z += 175.0; AngularImpulse angVelocity = RandomAngularImpulse( -500.0f, 500.0f ); breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity ); params.impactEnergyScale = 1.0f; params.defBurstScale = 100.0f; params.defCollisionGroup = COLLISION_GROUP_DEBRIS; PropBreakableCreateAll( GetModelIndex(), NULL, params, this, -1, true, true ); } //========================================================= // GetDeathActivity - determines the best type of death // anim to play. //========================================================= Activity CASW_Alien::GetDeathActivity ( void ) { Activity deathActivity; deathActivity = ACT_DIESIMPLE; if ( m_nDeathStyle == kDIE_FANCY ) { if ( m_bOnFire ) deathActivity = ( Activity ) ACT_DEATH_FIRE; else if ( m_bElectroStunned ) deathActivity = ( Activity ) ACT_DEATH_ELEC; else deathActivity = ( Activity ) ACT_DIE_FANCY; } //BaseClass::GetDeathActivity(); return deathActivity; } bool CASW_Alien::CanBecomeRagdoll( void ) { MDLCACHE_CRITICAL_SECTION(); int ragdollSequence = SelectWeightedSequence( ACT_DIERAGDOLL ); if ( m_nDeathStyle == kDIE_FANCY && !m_bTimeToRagdoll ) return false; //Can't cause we don't have a ragdoll sequence. if ( ragdollSequence == ACTIVITY_NOT_AVAILABLE ) return false; if ( GetFlags() & FL_TRANSRAGDOLL ) return false; return true; } bool CASW_Alien::CanDoFancyDeath() { if ( m_nDeathStyle == kDIE_BREAKABLE || m_nDeathStyle == kDIE_INSTAGIB ) return false; if ( GetNavType() != NAV_GROUND ) return false; if ( IsCurSchedule( SCHED_BURROW_WAIT, false ) || IsCurSchedule( SCHED_WAIT_FOR_CLEAR_UNBORROW, false ) || IsCurSchedule( SCHED_BURROW_OUT, false ) ) return false; if ( m_hMoveClone.Get() ) return false; if ( m_bOnFire ) { // do i have the custom death activities? if ( SelectWeightedSequence ( ( Activity ) ACT_DEATH_FIRE ) == ACTIVITY_NOT_AVAILABLE || RandomFloat() < 0.5f ) return false; } else if ( m_bElectroStunned ) { // do i have the custom death activities? if ( SelectWeightedSequence ( ( Activity ) ACT_DEATH_ELEC ) == ACTIVITY_NOT_AVAILABLE || RandomFloat() < 0.75f ) return false; } // do i have the custom death activities? else { if ( m_fFancyDeathChance > asw_alien_fancy_death_chance.GetFloat() ) return false; if ( SelectWeightedSequence ( ( Activity ) ACT_DIE_FANCY ) == ACTIVITY_NOT_AVAILABLE ) return false; } return true; } extern ConVar asw_fist_ragdoll_chance; void CASW_Alien::Event_Killed( const CTakeDamageInfo &info ) { if (asw_debug_alien_damage.GetBool()) Msg("%f alien killed\n", gpGlobals->curtime); if (ASWGameRules()) { ASWGameRules()->AlienKilled(this, info); } CASW_GameStats.Event_AlienKilled( this, info ); if ( ASWDirector() ) ASWDirector()->Event_AlienKilled( this, info ); if (m_hSpawner.Get()) m_hSpawner->AlienKilled(this); //bool bRagdollCreated = Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_ELECTRICAL ); //switch to the dead bodygroup if you are not on fire or electrocuted if ( HasDeadBodyGroup() && !m_bOnFire && !m_bElectroStunned ) { SetBodygroup( 0, m_iDeadBodyGroup ); } if ( m_flFrozen >= 0.1f ) { bool bShatter = ( RandomFloat() > 0.01f ); CreateASWServerStatue( this, COLLISION_GROUP_NONE, info, bShatter ); BaseClass::Event_Killed( CTakeDamageInfo( info.GetAttacker(), info.GetAttacker(), info.GetDamage(), DMG_GENERIC | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE ) ); RemoveDeferred(); return; } // if we died from an explosion, instagib if ( !m_bNeverInstagib && info.GetDamage() > 8.0f && (info.GetDamageType() & DMG_BLAST || info.GetDamageType() & DMG_SONIC) ) { m_nDeathStyle = kDIE_INSTAGIB; } else if ( !m_bNeverInstagib && !m_bElectroStunned && info.GetDamage() > 20.0f && RandomFloat() > 0.05f ) // if the damage inflicted was a high amount of damage, instagib 95% of the time { m_nDeathStyle = kDIE_INSTAGIB; } else // else see if we can break apart { // if we can do a fancy death or we don't do a break, we will ragdoll to the split version if ( CanDoFancyDeath() ) { m_nDeathStyle = kDIE_FANCY; } // if our model is set up to break and we aren't on fire or electrocuted, break else if ( CanBreak() && RandomFloat() < asw_alien_break_chance.GetFloat() && !m_bElectroStunned && !m_bOnFire ) { m_nDeathStyle = kDIE_BREAKABLE; //BreakAlien( info ); } // if we are set to never ragdoll or if we are electrified, but dont have anims or decided not to do them, instagib else if ( m_bNeverRagdoll || m_bElectroStunned ) { m_nDeathStyle = kDIE_INSTAGIB; } } if (!ShouldGib(info)) { const unsigned int nDamageTypesThatCauseHurling = DMG_BLAST | DMG_BLAST_SURFACE; SetCollisionGroup(ASW_COLLISION_GROUP_PASSABLE); // don't block marines by dead bodies if ( (info.GetDamageType() & nDamageTypesThatCauseHurling) && gpGlobals->curtime > sm_flLastHurlTime + asw_drone_hurl_interval.GetFloat() && random->RandomFloat() <= asw_drone_hurl_chance.GetFloat() ) { m_nDeathStyle = kDIE_HURL; sm_flLastHurlTime = gpGlobals->curtime; } if ( ( info.GetDamageType() & DMG_CLUB ) && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE ) { CASW_Marine *pMarine = assert_cast( info.GetAttacker() ); if ( pMarine->HasPowerFist() && RandomFloat() < asw_fist_ragdoll_chance.GetFloat() ) { m_nDeathStyle = kDIE_MELEE_THROW; } } } DropMoney( info ); if ( ASWGameRules() ) ASWGameRules()->DropPowerup( this, info, GetClassname() ); if ( asw_alien_debug_death_style.GetBool() ) Msg( "'%s' CASW_Alien::Event_Killed: m_nDeathStyle = %d\n", GetClassname(), m_nDeathStyle ); BaseClass::Event_Killed(info); } void CASW_Alien::SetSpawner(CASW_Base_Spawner* spawner) { m_hSpawner = spawner; } void CASW_Alien::InputBreakWaitForScript(inputdata_t &inputdata) { if (IsCurSchedule( SCHED_WAIT_FOR_SCRIPT )) { SetSchedule(SCHED_IDLE_STAND); } } // set orders for our alien // select schedule should activate the appropriate orders void CASW_Alien::SetAlienOrders(AlienOrder_t Orders, Vector vecOrderSpot, CBaseEntity* pOrderObject) { m_AlienOrders = Orders; m_vecAlienOrderSpot = vecOrderSpot; // unused currently m_AlienOrderObject = pOrderObject; if (Orders == AOT_None) { ClearAlienOrders(); return; } ForceDecisionThink(); // todo: stagger the decision time if at start of mission? //Msg("Drone recieved orders\n"); } AlienOrder_t CASW_Alien::GetAlienOrders() { return m_AlienOrders; } void CASW_Alien::ClearAlienOrders() { //Msg("Drone orders cleared\n"); m_AlienOrders = AOT_None; m_vecAlienOrderSpot = vec3_origin; m_AlienOrderObject = NULL; m_bIgnoreMarines = false; m_bFailedMoveTo = false; } void CASW_Alien::GatherConditions() { BaseClass::GatherConditions(); if (m_bIgnoreMarines) // todo: 'proper' way to ignore conditions? (using SetIgnoreConditions on receiving orders doesn't work) { ClearCondition(COND_CAN_MELEE_ATTACK1); ClearCondition(COND_CAN_MELEE_ATTACK2); ClearCondition(COND_CAN_RANGE_ATTACK1); ClearCondition(COND_CAN_RANGE_ATTACK2); ClearCondition(COND_ENEMY_DEAD); ClearCondition(COND_HEAR_BULLET_IMPACT); ClearCondition(COND_HEAR_COMBAT); ClearCondition(COND_HEAR_DANGER); ClearCondition(COND_HEAR_PHYSICS_DANGER); ClearCondition(COND_NEW_ENEMY); ClearCondition(COND_PROVOKED); ClearCondition(COND_SEE_ENEMY); ClearCondition(COND_SEE_FEAR); ClearCondition(COND_SMELL); ClearCondition(COND_HEAVY_DAMAGE); ClearCondition(COND_LIGHT_DAMAGE); ClearCondition(COND_RECEIVED_ORDERS); } if (HasCondition(COND_NEW_ENEMY) && m_AlienOrders != AOT_MoveToIgnoringMarines) // if we're not ignoring marines, finish our orders once we spot an enemy { ClearAlienOrders(); } } // if we arrive at our destination, clear our orders void CASW_Alien::OnMovementComplete() { if ( ShouldClearOrdersOnMovementComplete() ) { ClearAlienOrders(); } BaseClass::OnMovementComplete(); } bool CASW_Alien::ShouldClearOrdersOnMovementComplete() { if (m_AlienOrders != AOT_None) { if (m_AlienOrders == AOT_SpreadThenHibernate && m_bFailedMoveTo) { // should try to repath? // or go to a schedule where we wait X seconds, then try to repath? } // check if we finished trying to chase a marine, but don't have an enemy yet // this means the marine probably moved somewhere else and we need to chase after him again if (m_AlienOrders == AOT_MoveToNearestMarine && !GetEnemy()) { float marine_distance; CBaseEntity *pMarine = UTIL_ASW_NearestMarine(GetAbsOrigin(), marine_distance); if (pMarine) { // don't clear orders, this'll make the alien's selectschedule do the marine chase again return false; } } } return true; }; void CASW_Alien::IgnoreMarines(bool bIgnoreMarines) { static int g_GeneralConditions[] = { COND_CAN_MELEE_ATTACK1, COND_CAN_MELEE_ATTACK2, COND_CAN_RANGE_ATTACK1, COND_CAN_RANGE_ATTACK2, COND_ENEMY_DEAD, COND_HEAR_BULLET_IMPACT, COND_HEAR_COMBAT, COND_HEAR_DANGER, COND_HEAR_PHYSICS_DANGER, COND_NEW_ENEMY, COND_PROVOKED, COND_SEE_ENEMY, COND_SEE_FEAR, COND_SMELL, }; static int g_DamageConditions[] = { COND_HEAVY_DAMAGE, COND_LIGHT_DAMAGE, COND_RECEIVED_ORDERS, }; ClearIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) ); ClearIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) ); if (bIgnoreMarines) { SetIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) ); SetIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) ); m_bIgnoreMarines = true; } } // we're blocking a fellow alien from spawning, let's move a short distance void CASW_Alien::MoveAside() { if (!GetEnemy() && !IsMoving()) { // random nearby position if ( !GetNavigator()->SetWanderGoal( 90, 200 ) ) { if ( !GetNavigator()->SetRandomGoal( 150.0f ) ) { return; // couldn't find a wander spot } } //SetSchedule(SCHED_IDLE_WALK); } } void CASW_Alien::ASW_Ignite( float flFlameLifetime, float flSize, CBaseEntity *pAttacker, CBaseEntity *pDamagingWeapon ) { if (AllowedToIgnite()) { if( IsOnFire() ) return; AddFlag( FL_ONFIRE ); m_bOnFire = true; if (ASWBurning()) ASWBurning()->BurnEntity(this, pAttacker, flFlameLifetime, 0.4f, 2.5f * 0.4f, pDamagingWeapon ); // 2.5 dps, applied every 0.4 seconds m_OnIgnite.FireOutput( this, this ); } } void CASW_Alien::Extinguish() { m_bOnFire = false; if (ASWBurning()) ASWBurning()->Extinguish(this); RemoveFlag( FL_ONFIRE ); } bool CASW_Alien::IsMeleeAttacking() { return (GetTask() && (GetTask()->iTask == TASK_MELEE_ATTACK1)); } bool CASW_Alien::Knockback(Vector vecForce) { /* Vector end = GetAbsOrigin() + vecForce; // todo: divide by mass, etc trace_t tr; UTIL_TraceEntity( this, GetAbsOrigin(), end, MASK_NPCSOLID, &tr ); if (tr.allsolid) return false; if( tr.fraction > 0 ) { SetAbsOrigin(tr.endpos); return true; } return false;*/ Vector newVel = GetAbsVelocity(); SetAbsVelocity(newVel + vecForce); SetGroundEntity( NULL ); return true; } void CASW_Alien::UpdateEfficiency( bool bInPVS ) { // Sleeping NPCs always dormant if ( GetSleepState() != AISS_AWAKE ) { SetEfficiency( AIE_DORMANT ); return; } m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) ); #ifndef _RETAIL if ( !(ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool()) ) { SetEfficiency( AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); return; } #endif bool bInVisibilityPVS = ( UTIL_FindClientInVisibilityPVS( edict() ) != NULL ); //if ( bInPVS && MarineNearby(1024) ) if ( bInPVS && MarineCanSee(384, 1.0f) ) { SetMoveEfficiency( AIME_NORMAL ); } else { SetMoveEfficiency( AIME_EFFICIENT ); } //--------------------------------- if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT ) { SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() ); return; } // Some conditions will always force normal if ( gpGlobals->curtime - GetLastAttackTime() < .15 ) { SetEfficiency( AIE_NORMAL ); return; } bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() ); if ( IsForceGatherConditionsSet() || gpGlobals->curtime - GetLastAttackTime() < .2 || gpGlobals->curtime - m_flLastDamageTime < .2 || ( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) || ( ( bInPVS || bInVisibilityPVS ) && ( ( GetTask() && !TaskIsRunning() ) || GetTaskInterrupt() > 0 || m_bInChoreo ) ) ) { SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT ); return; } SetEfficiency( ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT ); } void CASW_Alien::OnRestore() { BaseClass::OnRestore(); m_LagCompensation.Init(this); } // checks if a marine can see us // caches the results and won't recheck unless the specified interval has passed since the last check bool CASW_Alien::MarineCanSee(int padding, float interval) { if (gpGlobals->curtime >= m_fLastMarineCanSeeTime + interval) { bool bCorpseCanSee = false; m_bLastMarineCanSee = (UTIL_ASW_AnyMarineCanSee(GetAbsOrigin(), padding, bCorpseCanSee) != NULL) || bCorpseCanSee; m_fLastMarineCanSeeTime = gpGlobals->curtime; } return m_bLastMarineCanSee; } void CASW_Alien::SetHealthByDifficultyLevel() { // filled in by subclasses } void CASW_Alien::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) { return; // use ASW_Ignite instead } bool CASW_Alien::ShouldMoveSlow() const { return (gpGlobals->curtime < m_fHurtSlowMoveTime); } bool CASW_Alien::ModifyAutoMovement( Vector &vecNewPos ) { // melee auto movement on the drones seems way too fast float fFactor = 1.0f; if ( ShouldMoveSlow() ) { if ( m_bElectroStunned.Get() ) { fFactor *= asw_alien_stunned_speed.GetFloat() * 0.1f; } else { fFactor *= asw_alien_hurt_speed.GetFloat() * 0.1f; } Vector vecRelPos = vecNewPos - GetAbsOrigin(); vecRelPos *= fFactor; vecNewPos = GetAbsOrigin() + vecRelPos; return true; } return false; } float CASW_Alien::GetIdealSpeed() const { // if the alien is hurt, move slower if ( ShouldMoveSlow() ) { if ( m_bElectroStunned.Get() ) { return BaseClass::GetIdealSpeed() * asw_alien_stunned_speed.GetFloat(); } else { return BaseClass::GetIdealSpeed() * asw_alien_hurt_speed.GetFloat(); } } return BaseClass::GetIdealSpeed(); } void CASW_Alien::DropMoney( const CTakeDamageInfo &info ) { if ( !asw_drop_money.GetBool() || !asw_money.GetBool() ) return; int iMoneyCount = GetMoneyCount( info ); for (int i=0;iSpawn(); Vector vel = -info.GetDamageForce(); vel.NormalizeInPlace(); vel *= RandomFloat( 30, 50 ); vel += RandomVector( -20, 20 ); vel.z = RandomFloat( 30, 50 ); if ( iMoneyCount >= 3 ) // if we're dropping a lot of money, make it fly up in the air more { vel.z += RandomFloat( 60, 80 ); } pMoney->SetAbsVelocity( vel ); } } int CASW_Alien::GetMoneyCount( const CTakeDamageInfo &info ) { if ( RandomFloat() < asw_alien_money_chance.GetFloat() ) { return 1; } return 0; } void CASW_Alien::ElectroStun( float flStunTime ) { if (m_fHurtSlowMoveTime < gpGlobals->curtime + flStunTime) m_fHurtSlowMoveTime = gpGlobals->curtime + flStunTime; m_bElectroStunned = true; if ( ASWGameResource() ) { ASWGameResource()->m_iElectroStunnedAliens++; } // can't jump after being elecrostunned CapabilitiesRemove( bits_CAP_MOVE_JUMP ); } void CASW_Alien::ForceFlinch( const Vector &vecSrc ) { SetCondition( COND_HEAVY_DAMAGE ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CASW_AlienVolley *CASW_Alien::GetVolley( const char *pszVolleyName ) { for( int nIndex = 0; nIndex < m_volleys.Count(); nIndex++ ) { if ( m_volleys[ nIndex ].m_strName == pszVolleyName ) { return &m_volleys[ nIndex ]; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CASW_Alien::GetVolleyIndex( const char *pszVolleyName ) { for( int nIndex = 0; nIndex < m_volleys.Count(); nIndex++ ) { if ( m_volleys[ nIndex ].m_strName == pszVolleyName ) { return nIndex; } } return -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CASW_AlienShot *CASW_Alien::GetShot( const char *pszShotName ) { for( int nIndex = 0; nIndex < m_shots.Count(); nIndex++ ) { if ( m_shots[ nIndex ].m_strName == pszShotName ) { return &m_shots[ nIndex ]; } } return NULL; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CASW_Alien::GetShotIndex( const char *pszShotName ) { for( int nIndex = 0; nIndex < m_shots.Count(); nIndex++ ) { if ( m_shots[ nIndex ].m_strName == pszShotName ) { return nIndex; } } return -1; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Alien::CreateShot( const char *pszShotName, const CASW_AlienShot *pShot ) { CASW_AlienShot *pNewShot = GetShot( pszShotName ); if ( !pNewShot ) { int nIndex = m_shots.AddToTail(); pNewShot = &m_shots[ nIndex ]; } *pNewShot = *pShot; pNewShot->m_strName = pszShotName; // Precache resources if ( !pShot->m_strModel.IsEmpty() ) PrecacheModel( pShot->m_strModel ); if ( !pShot->m_strSound_spawn.IsEmpty() ) PrecacheScriptSound( pShot->m_strSound_spawn ); if ( !pShot->m_strSound_hitNPC.IsEmpty() ) PrecacheScriptSound( pShot->m_strSound_hitNPC ); if ( !pShot->m_strSound_hitWorld.IsEmpty() ) PrecacheScriptSound( pShot->m_strSound_hitWorld ); if ( !pShot->m_strParticles_trail.IsEmpty() ) PrecacheParticleSystem( pShot->m_strParticles_trail ); if ( !pShot->m_strParticles_hit.IsEmpty() ) PrecacheParticleSystem( pShot->m_strParticles_hit ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Alien::CreateVolley( const char *pszVolleyName, const CASW_AlienVolley *pVolley ) { CASW_AlienVolley *pNewVolley = GetVolley( pszVolleyName ); if ( !pNewVolley ) { int nIndex = m_volleys.AddToTail(); pNewVolley = &m_volleys[ nIndex ]; } *pNewVolley = *pVolley; pNewVolley->m_strName = pszVolleyName; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Alien::CalcMissileVelocity( const Vector &vTargetPos, Vector &vVelocity, float flSpeed, float flAngle ) { float flRadians = DEG2RAD( flAngle ); Vector vForward = ( vTargetPos - GetAbsOrigin() ); vForward.NormalizeInPlace(); Vector vRight = vForward.Cross( Vector( 0, 0, 1 ) ); Vector vDir = vForward * cos( flRadians ) + vRight * sin( flRadians ); vVelocity = vDir * flSpeed; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Alien::UpdateRangedAttack() { if ( m_nVolleyType < 0 ) return; Vector vecSpawn = GetAbsOrigin() + Vector( 0.0f, 0.0f, 15.0f ); QAngle vecAngles = QAngle( 0.0f, 0.0f, 0.0f ); int iAttachment = LookupAttachment( "mouth" ); if ( iAttachment > 0 ) GetAttachment( iAttachment, vecSpawn, vecAngles ); Vector vForward = ( m_vecRangeAttackTargetPosition- GetAbsOrigin() ); vForward.NormalizeInPlace(); Vector vRight = vForward.Cross( Vector( 0, 0, 1 ) ); int nNumFinished = 0; CASW_AlienVolley *pVolley = &m_volleys[ m_nVolleyType ]; for( int nRound = 0; nRound < pVolley->m_rounds.Count(); nRound++ ) { CASW_AlienVolleyRound &round = pVolley->m_rounds[ nRound ]; if ( round.m_nShot_type < 0 ) { nNumFinished++; continue; } // has round started yet? if ( m_flRangeAttackStartTime + round.m_flTime > gpGlobals->curtime ) continue; // is round finished? float flEndTime = m_flRangeAttackStartTime + round.m_flTime + round.m_nNumShots * round.m_flShotDelay; if ( ( flEndTime < gpGlobals->curtime ) && ( flEndTime < m_flRangeAttackLastUpdateTime ) ) { nNumFinished++; continue; } for( int i = 0; i < round.m_nNumShots; i++ ) { float flShotTime = m_flRangeAttackStartTime + round.m_flTime + i * round.m_flShotDelay; if ( ( flShotTime > m_flRangeAttackLastUpdateTime ) && ( flShotTime <= gpGlobals->curtime ) ) { Vector vVelocity; CASW_AlienShot &shot = m_shots[ round.m_nShot_type ]; float flPercent = ( round.m_nNumShots > 1 ) ? ( ( float )i / ( float )( round.m_nNumShots - 1 ) ) : 0.0f; float flRadians = DEG2RAD( Lerp( flPercent, round.m_flStartAngle, round.m_flEndAngle ) ); Vector vDir = vForward * cos( flRadians ) + vRight * sin( flRadians ); vVelocity = vDir * round.m_flSpeed; Vector vStart = vecSpawn + vRight * round.m_flHorizontalOffset; CASW_Missile_Round::Missile_Round_Create( shot, vStart, vecAngles, vVelocity, this ); } } } if ( nNumFinished >= pVolley->m_rounds.Count() ) { m_nVolleyType = -1; return; } m_flRangeAttackLastUpdateTime = gpGlobals->curtime; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Alien::LaunchMissile( const char *pszVolleyName, Vector &vTargetPosition ) { m_nVolleyType = GetVolleyIndex( pszVolleyName ); if ( m_nVolleyType < 0 ) { //FIXME: add warning return; } m_flRangeAttackStartTime = gpGlobals->curtime; m_flRangeAttackLastUpdateTime = -1.0f; m_vecRangeAttackTargetPosition = vTargetPosition; UpdateRangedAttack(); } void CASW_Alien::AddBehaviorParam( const char *pszParmName, int nDefaultValue ) { CUtlSymbol ParmName = CAI_ASW_Behavior::GetSymbol( pszParmName ); m_BehaviorParms.Insert( ParmName, nDefaultValue ); } int CASW_Alien::GetBehaviorParam( CUtlSymbol ParmName ) { int nIndex = m_BehaviorParms.Find( ParmName ); if ( m_BehaviorParms.IsValidIndex( nIndex ) == true ) { return m_BehaviorParms.Element( nIndex ); } return 0; } void CASW_Alien::SetBehaviorParam( CUtlSymbol ParmName, int nValue ) { if ( ParmName.IsValid() == false ) { return; } int nIndex = m_BehaviorParms.Find( ParmName ); if ( m_BehaviorParms.IsValidIndex( nIndex ) == true ) { if ( m_BehaviorParms.Element( nIndex ) != nValue ) { m_BehaviorParms.Element( nIndex ) = nValue; m_bBehaviorParameterChanged = true; } } else { Assert( 0 ); } } void CASW_Alien::OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior ) { BaseClass::OnChangeRunningBehavior( pOldBehavior, pNewBehavior ); m_pPreviousBehavior = m_pPreviousBehavior; } void CASW_Alien::SendBehaviorEvent( CBaseEntity *pInflictor, BehaviorEvent_t Event, int nParm, bool bToAllBehaviors ) { if ( bToAllBehaviors ) { for ( int i = 0; i < m_Behaviors.Count(); i++) { CAI_ASW_Behavior *pBehavior = static_cast < CAI_ASW_Behavior * >( m_Behaviors[ i ] ); pBehavior->HandleBehaviorEvent( pInflictor, Event, nParm ); } } else { CAI_ASW_Behavior *pBehavior = GetPrimaryASWBehavior(); if ( pBehavior == NULL ) { // we have probably died, try sending this to the last behavior pBehavior = static_cast < CAI_ASW_Behavior * >( m_pPreviousBehavior ); } if ( pBehavior != NULL ) { pBehavior->HandleBehaviorEvent( pInflictor, Event, nParm ); } else { // some how the boomer is coming back with having no primary behavior, which I don't understand. // if you see this assert fire, please let RJ know. Assert( 0 ); } } } void CASW_Alien::BuildScheduleTestBits() { CAI_ASW_CombatStunBehavior *pBehavior = NULL; // Check for combat stun behavior, or, if we're not using behaviors, we'll fall back to the combat stun schedule in CASW_Alien if ( GetBehavior( &pBehavior ) && pBehavior != m_pPrimaryBehavior ) { SetCustomInterruptCondition( COND_ASW_BEGIN_COMBAT_STUN ); } SetCustomInterruptCondition( COND_BEHAVIOR_PARAMETERS_CHANGED ); if ( m_pFlinchBehavior ) { SetCustomInterruptCondition( COND_ASW_FLINCH ); } BaseClass::BuildScheduleTestBits(); } void CASW_Alien::StartTouch( CBaseEntity *pOther ) { BaseClass::StartTouch(pOther); CAI_ASW_Behavior *pCurrent = GetPrimaryASWBehavior(); if ( pCurrent ) { pCurrent->StartTouch( pOther ); } } CAI_ASW_Behavior* CASW_Alien::GetPrimaryASWBehavior() { return assert_cast( GetPrimaryBehavior() ); } void CASW_Alien::HandleAnimEvent( animevent_t *pEvent ) { int nEvent = pEvent->Event(); CTakeDamageInfo info; if ( nEvent == AE_NPC_RAGDOLL ) { m_bTimeToRagdoll = true; // anim event is telling us to ragdoll. If we aren't on fire, break instead if ( m_nDeathStyle == kDIE_FANCY && !m_bOnFire ) { m_nDeathStyle = kDIE_BREAKABLE; //BreakAlien( info ); } //BecomeRagdollOnClient(GetAbsOrigin()); //return; } CAI_ASW_Behavior *pBehavior = GetPrimaryASWBehavior(); if ( pBehavior && pBehavior->BehaviorHandleAnimEvent( pEvent ) ) return; BaseClass::HandleAnimEvent( pEvent ); } int CASW_Alien::DrawDebugTextOverlays() { int text_offset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Freeze amt.: %f", m_flFrozen.Get() ),0 ); text_offset++; NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Freeze time: %f", m_flFrozenTime - gpGlobals->curtime ),0 ); text_offset++; } return text_offset; } void CASW_Alien::InputSetMoveClone( inputdata_t &inputdata ) { SetMoveClone( inputdata.value.StringID(), inputdata.pActivator ); } void CASW_Alien::SetMoveClone( string_t EntityName, CBaseEntity *pActivator ) { CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, EntityName, NULL, pActivator ); if ( EntityName != NULL_STRING && pEnt == NULL ) { Msg( "Entity %s(%s) has bad move clone %s\n", STRING(m_iClassname), GetDebugName(), STRING(EntityName) ); } else { // make sure there isn't any ambiguity if ( gEntList.FindEntityByName( pEnt, EntityName, NULL, pActivator ) ) { Msg( "Entity %s(%s) is ambiguously move cloned to %s, because there is more than one entity by that name.\n", STRING(m_iClassname), GetDebugName(), STRING(EntityName) ); } SetMoveClone( pEnt ); } } void CASW_Alien::SetMoveClone( CBaseEntity *pEnt ) { m_hMoveClone = pEnt; if ( pEnt ) { matrix3x4_t entityToWorld = EntityToWorldTransform(); matrix3x4_t otherToWorld = pEnt->EntityToWorldTransform(); matrix3x4_t temp; MatrixInvert( otherToWorld, temp ); ConcatTransforms( temp, entityToWorld, m_moveCloneOffset ); } } bool CASW_Alien::OverrideMove( float flInterval ) { if ( m_hMoveClone.Get() ) { matrix3x4_t otherToWorld = m_hMoveClone->EntityToWorldTransform(); matrix3x4_t temp; ConcatTransforms( otherToWorld, m_moveCloneOffset, temp ); SetLocalTransform( temp ); return true; } return false; } void CASW_Alien::ClearBurrowPoint( const Vector &origin ) { CBaseEntity *pEntity = NULL; float flDist; Vector vecSpot, vecCenter, vecForce; //Cause a ruckus //UTIL_ScreenShake( origin, 1.0f, 80.0f, 1.0f, 256.0f, SHAKE_START ); //Iterate on all entities in the vicinity. for ( CEntitySphereQuery sphere( origin, 128 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() ) { //if ( pEntity->m_takedamage != DAMAGE_NO && pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() ) if ( pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() ) { vecSpot = pEntity->BodyTarget( origin ); vecForce = ( vecSpot - origin ) + Vector( 0, 0, 16 ); // decrease damage for an ent that's farther from the bomb. flDist = VectorNormalize( vecForce ); //float mass = pEntity->VPhysicsGetObject()->GetMass(); CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1.0f, 1.0f, 1.0f ), &vecCenter ); if ( flDist <= 128.0f ) { pEntity->VPhysicsGetObject()->Wake(); pEntity->VPhysicsGetObject()->ApplyForceOffset( vecForce * 250.0f, vecCenter ); } } } } void CASW_Alien::Unburrow( void ) { m_bStartBurrowed = false; ClearBurrowPoint( m_vecUnburrowEndPoint ); // physics blast anything in the way out of the way //Become solid again and visible m_spawnflags &= ~SF_NPC_GAG; RemoveSolidFlags( FSOLID_NOT_SOLID ); m_takedamage = DAMAGE_YES; RemoveEffects( EF_NODRAW ); RemoveFlag( FL_NOTARGET ); SetIdealActivity( m_UnburrowActivity ); //If we have an enemy, come out facing them /* if ( GetEnemy() ) { Vector dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize(dir); QAngle angles = GetAbsAngles(); angles[ YAW ] = UTIL_VecToYaw( dir ); SetLocalAngles( angles ); }*/ } void CASW_Alien::SetUnburrowActivity( string_t iszActivityName ) { m_iszUnburrowActivityName = iszActivityName; } void CASW_Alien::SetUnburrowIdleActivity( string_t iszActivityName ) { m_iszUnburrowIdleActivityName = iszActivityName; } void CASW_Alien::LookupBurrowActivities() { if ( m_iszUnburrowActivityName == NULL_STRING ) { m_UnburrowActivity = (Activity) ACT_BURROW_OUT; } else { m_UnburrowActivity = (Activity) LookupActivity( STRING( m_iszUnburrowActivityName ) ); if ( m_UnburrowActivity == ACT_INVALID ) { Warning( "Unknown unburrow activity %s", STRING( m_iszUnburrowActivityName ) ); if ( m_hSpawner.Get() ) { Warning( " Spawner is: %d %s at %f %f %f\n", m_hSpawner->entindex(), m_hSpawner->GetDebugName(), VectorExpand( m_hSpawner->GetAbsOrigin() ) ); } m_UnburrowActivity = (Activity) ACT_BURROW_OUT; } } if ( m_iszUnburrowIdleActivityName == NULL_STRING ) { m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE; } else { m_UnburrowIdleActivity = (Activity) LookupActivity( STRING( m_iszUnburrowIdleActivityName ) ); if ( m_UnburrowActivity == ACT_INVALID ) { Warning( "Unknown unburrow idle activity %s", STRING( m_iszUnburrowIdleActivityName ) ); if ( m_hSpawner.Get() ) { Warning( " Spawner is: %d %s at %f %f %f\n", m_hSpawner->entindex(), m_hSpawner->GetDebugName(), VectorExpand( m_hSpawner->GetAbsOrigin() ) ); } m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE; } } } class CASW_Trace_Filter_Disable_Collision_With_Traps : public CTraceFilterEntitiesOnly { public: // It does have a base, but we'll never network anything below here.. DECLARE_CLASS_NOBASE( CASW_Trace_Filter_Disable_Collision_With_Traps ); CASW_Trace_Filter_Disable_Collision_With_Traps( IHandleEntity *passentity, int collisionGroup ) : m_pPassEnt(passentity), m_collisionGroup(collisionGroup) { } virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { if ( !StandardFilterRules( pHandleEntity, contentsMask ) ) return false; // Don't test if the game code tells us we should ignore this collision... CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); CBaseEntity *pEntPass = EntityFromEntityHandle( m_pPassEnt ); // don't hurt ourself if ( pEntPass == pEntity ) return false; Class_T entClass = pEntity->Classify(); if ( entClass == CLASS_ASW_SENTRY_BASE || entClass == CLASS_ASW_TESLA_TRAP ) { PhysDisableEntityCollisions( pEntPass, pEntity ); } return false; // keep iterating over entities in the trace ray } public: IHandleEntity *m_pPassEnt; int m_collisionGroup; }; // if the alien is stuck inside any player set traps, this will disable collision between them void CASW_Alien::CheckForBlockingTraps() { Ray_t ray; trace_t tr; ray.Init( GetAbsOrigin() + Vector( 0, 0, 1 ), GetAbsOrigin(), GetHullMins(), GetHullMaxs() ); CASW_Trace_Filter_Disable_Collision_With_Traps traceFilter( this, GetCollisionGroup() ); enginetrace->TraceRay( ray, PhysicsSolidMaskForEntity(), &traceFilter, &tr ); } void CASW_Alien::SetDefaultEyeOffset() { m_vDefaultEyeOffset = vec3_origin; m_vDefaultEyeOffset.z = ( WorldAlignMins().z + WorldAlignMaxs().z ) * 0.75f; SetViewOffset( m_vDefaultEyeOffset ); }