//=================== Half-Life 2: Short Stories Mod 2007 =====================// // // Purpose: AlienGrunts from HL1 now in updated form // by Au-heppa // //=============================================================================// #include "cbase.h" #include "beam_shared.h" #include "game.h" // For skill levels #include "globalstate.h" #include "npc_talker.h" #include "ai_motor.h" #include "ai_schedule.h" #include "scripted.h" #include "basecombatweapon.h" #include "soundent.h" #include "npcevent.h" #include "ai_hull.h" #include "animation.h" #include "ammodef.h" // For DMG_CLUB #include "Sprite.h" #include "npc_aliengrunt.h" //#include "npc_aliencontroller.h" #include "activitylist.h" #include "player.h" #include "items.h" #include "vehicle_base.h" //#include "basegrenade_shared.h" #include "ai_interactions.h" #include "IEffects.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "globals.h" #include "effect_dispatch_data.h" #include "te_effect_dispatch.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "physics_prop_ragdoll.h" #include "RagdollBoogie.h" #include "physics_bone_follower.h" #include "physics_prop_ragdoll.h" #include "bone_setup.h" #include "weapon_physcannon.h" #include "weapon_bee.h" #include "ai_hint.h" #include "ai_network.h" #include "ai_pathfinder.h" #include "ai_navigator.h" #include "ai_senses.h" #include "ai_squadslot.h" #include "ai_squad.h" #include "ai_moveprobe.h" #include "basepropdoor.h" #include "doors.h" #include "vphysics_interface.h" #include "vphysics/constraints.h" #include "physics_saverestore.h" //#include "Human_Error/hlss_combine_interface.h" #include "npc_playercompanion.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #include "BloodDripsGreen.h" inline void TraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin, const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore, int collisionGroup, trace_t *ptr, float minMass ); #define ALIENGRUNT_LEFT_HAND "lefthand" #define ALIENGRUNT_RIGHT_HAND "righthand" static const char *AGRUNT_ATTACK = "AGRUNT_ATTACK"; static const char *AGRUNT_ALERT = "AGRUNT_ALERT"; static const char *AGRUNT_HIDE = "AGRUNT_HIDE"; static const char *AGRUNT_CHARGE = "AGRUNT_CHARGE"; //========================================================= // ALIENGRUNT General Attacking & Health //========================================================= ConVar sk_aliengrunt_health( "sk_aliengrunt_health","0"); ConVar sk_aliengrunt_dmg_claw( "sk_aliengrunt_dmg_claw","0"); ConVar aliengrunt_ragdoll_grab_delay("oc_aliengrunt_ragdoll_grab_delay", "4"); ConVar aliengrunt_shoot_bees_delay("oc_aliengrunt_shoot_bees_delay", "1"); #define ALIENGRUNT_NEXT_ATTACK_DELAY_SMALL 0.55f #define ALIENGRUNT_NEXT_ATTACK_DELAY random->RandomFloat( aliengrunt_shoot_bees_delay.GetFloat(), aliengrunt_shoot_bees_delay.GetFloat() + 1.0f ) //#define ALIENGRUNT_NEXT_ATTACK_DELAY_WHILE_CHARGE random->RandomFloat( aliengrunt_shoot_bees_delay.GetFloat() + 1.0f, aliengrunt_shoot_bees_delay.GetFloat() + 3.0f ) #define ALIENGRUNT_TOO_CLOSE_FOR_BEES 70 #define ALIENGRUNT_FACE_ENEMY_DISTANCE 512.0f #define ALIENGRUNT_FACE_PHYSICS_ENT_DISTANCE 128.0f //========================================================= // ALIENGRUNT Ragdoll Grabbing //========================================================= #define ALIENGRUNT_ALWAYS_RAGDOLL_GRAB #ifdef ALIENGRUNT_ALWAYS_RAGDOLL_GRAB static ConVar aliengrunt_always_ragdoll_grab("oc_aliengrunt_always_ragdoll_grab","0"); #endif //========================================================= // ALIENGRUNT Charge //========================================================= ConVar aliengrunt_charge_delay_max("oc_aliengrunt_charge_delay_max", "6"); ConVar aliengrunt_charge_delay_min("oc_aliengrunt_charge_delay", "2"); #define ALIENGRUNT_CHARGE_MIN 128 //Antlionguard has 256 #define ALIENGRUNT_CHARGE_MAX 768 //Antlionguard has 2048, I originally had 512 there because Alien Grunt is woulnerable during the charge #define ALIENGRUNT_CHARGE_CANCEL_TIME random->RandomFloat( 2.0, 3.0 ) //========================================================= // ALIENGRUNT activities //========================================================= Activity ACT_ALIENGRUNT_TO_ACTION; Activity ACT_ALIENGRUNT_TO_IDLE; Activity ACT_ALIENGRUNT_CHARGE_START; Activity ACT_ALIENGRUNT_CHARGE_LOOP; Activity ACT_ALIENGRUNT_CHARGE_HIT; Activity ACT_ALIENGRUNT_CHARGE_CANCEL; #ifdef ALIENGRUNT_GRAB_RAGDOLL Activity ACT_ALIENGRUNT_GRAB_RAGDOLL; #endif //========================================================= // ALIENGRUNT Anim Events //========================================================= int AE_ALIENGRUNT_HAND_LEFT; int AE_ALIENGRUNT_HAND_RIGHT; int AE_ALIENGRUNT_SHOOTBEES; //int AE_ALIENGRUNT_SHOOTBEES_DONE; int AE_ALIENGRUNT_SWING_SOUND; #ifdef ALIENGRUNT_GRAB_RAGDOLL int AE_ALIENGRUNT_GRAB_RAGDOLL; int AE_ALIENGRUNT_THROW_RAGDOLL; #endif int AE_ALIENGRUNT_CHARGE_HIT; #define ALIENGRUNT_CUSTOM_FOOT_AE #ifdef ALIENGRUNT_CUSTOM_FOOT_AE int AE_ALIENGRUNT_FOOT_LEFT; int AE_ALIENGRUNT_FOOT_RIGHT; #endif enum { SQUAD_SLOT_ALIENGRUNT_CHARGE = LAST_SHARED_SQUADSLOT, }; //----------------------------------------------------------------------------- // Interactions //----------------------------------------------------------------------------- //int g_interactionALIENGRUNTStomp = 0; //int g_interactionALIENGRUNTStompFail = 0; //int g_interactionALIENGRUNTStompHit = 0; //int g_interactionALIENGRUNTKick = 0; //int g_interactionALIENGRUNTClaw = 0; //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CNPC_AlienGrunt ) DEFINE_FIELD( m_HideSoundTime, FIELD_TIME), //DEFINE_FIELD( m_nextLineFireTime, FIELD_TIME), DEFINE_FIELD( m_iLeftHandAttachment, FIELD_INTEGER ), DEFINE_FIELD( m_iRightHandAttachment, FIELD_INTEGER ), DEFINE_FIELD( m_bShouldAim, FIELD_BOOLEAN ), DEFINE_FIELD( m_flNextSwat, FIELD_TIME ), DEFINE_FIELD( m_flNextSwatScan, FIELD_TIME ), DEFINE_FIELD( m_hPhysicsEnt, FIELD_EHANDLE ), DEFINE_FIELD( m_hObstructor, FIELD_EHANDLE ), DEFINE_FIELD( m_hSetTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_hDoor, FIELD_EHANDLE ), DEFINE_FIELD( m_iBees, FIELD_INTEGER ), DEFINE_FIELD( m_flNextChargeTime, FIELD_TIME ), DEFINE_FIELD( m_bDecidedNotToStop, FIELD_BOOLEAN ), DEFINE_FIELD( m_flChargeCancelTime, FIELD_TIME ), DEFINE_FIELD( m_flBurnDamage, FIELD_FLOAT ), DEFINE_FIELD( m_flBurnDamageResetTime, FIELD_TIME ), #ifdef ALIENGRUNT_GRAB_RAGDOLL DEFINE_FIELD( m_hRagdoll, FIELD_EHANDLE ), DEFINE_FIELD( m_flNextRagdollGrabTime, FIELD_TIME ), DEFINE_PHYSPTR( m_hPlayerConstrain ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableRagdollGrab", InputEnableRagdollGrab ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableRagdollGrab", InputDisableRagdollGrab ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableWaitRagdollGrab", InputDisableWaitRagdollGrab ), DEFINE_INPUTFUNC( FIELD_STRING, "AttackItem", InputAttackItem ), #endif DEFINE_FIELD(m_flDamageTime, FIELD_TIME), // BJ: Armor hit react END_DATADESC() LINK_ENTITY_TO_CLASS( npc_aliengrunt, CNPC_AlienGrunt ); //----------------------------------------------------------------------------- // Purpose: back away from overly close zombies //----------------------------------------------------------------------------- Disposition_t CNPC_AlienGrunt::IRelationType( CBaseEntity *pTarget ) { /*CBasePlayer *pPlayer = ToBasePlayer( pTarget ); if (pPlayer && pPlayer->IsRevealedTraitor()) { return D_LI; }*/ return BaseClass::IRelationType( pTarget ); } #ifdef ALIENGRUNT_GRAB_RAGDOLL const char *pFollowerBoneNames[] = { "ValveBiped.Bip01_R_Forearm", "ValveBiped.Bip01_Head1" }; void CNPC_AlienGrunt::InputEnableRagdollGrab( inputdata_t &inputdata ) { AddSpawnFlags( SF_ALIENGRUNT_CAN_GRAB_RAGDOLL ); } void CNPC_AlienGrunt::InputDisableRagdollGrab( inputdata_t &inputdata ) { RemoveSpawnFlags( SF_ALIENGRUNT_CAN_GRAB_RAGDOLL ); } void CNPC_AlienGrunt::InputDisableWaitRagdollGrab( inputdata_t &inputdata ) { RemoveSpawnFlags( SF_ALIENGRUNT_WAIT_FOR_GRAB ); } void CNPC_AlienGrunt::Event_Killed( const CTakeDamageInfo &info ) { // m_BoneFollowerManager.DestroyBoneFollowers(); if (!DestroyPlayerConstrain() && m_hRagdoll) { CBaseEntity *pRagdoll = m_hRagdoll; if ( pRagdoll ) { DetachAttachedRagdoll( pRagdoll ); } m_hRagdoll = NULL; } BaseClass::Event_Killed( info ); } //--------------------------------------------------------- //--------------------------------------------------------- /*static Vector GetAttachmentPositionInSpaceOfBone( CStudioHdr *pStudioHdr, const char *pAttachmentName, int outputBoneIndex ) { int attachment = Studio_FindAttachment( pStudioHdr, pAttachmentName ); Vector localAttach; const mstudioattachment_t &pAttachment = pStudioHdr->pAttachment(attachment); int iBone = pStudioHdr->GetAttachmentBone( attachment ); MatrixGetColumn( pAttachment.local, 3, localAttach ); matrix3x4_t inputToOutputBone; Studio_CalcBoneToBoneTransform( pStudioHdr, iBone, outputBoneIndex, inputToOutputBone ); Vector out; VectorTransform( localAttach, inputToOutputBone, out ); return out; }*/ //------------------------------------- void CNPC_AlienGrunt::InputAttackItem( inputdata_t &inputdata ) { CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller ); if (!pTarget) { Warning("npc_aliengrunt: InputAttackItem, %s could not be found\n"); return; } if (FClassnameIs(pTarget, "prop_physics") || FClassnameIs(pTarget, "func_physics") || FClassnameIs(pTarget, "prop_door_rotating")) { m_hSetTarget = pTarget; } else { Warning("npc_aliengrunt: InputAttackItem, %s is not correct type of entity\n"); } } //--------------------------------------------------------- //--------------------------------------------------------- void CNPC_AlienGrunt::UpdateOnRemove() { DestroyPlayerConstrain(); BaseClass::UpdateOnRemove(); } bool CNPC_AlienGrunt::DestroyPlayerConstrain(bool bThrow) { if (m_hPlayerConstrain != NULL) { if (m_hPlayerConstrain->GetAttachedObject() && m_hPlayerConstrain->GetAttachedObject()->GetGameData()) { CBasePlayer *pPlayer = static_cast( m_hPlayerConstrain->GetAttachedObject()->GetGameData() ); if (pPlayer) { //DevMsg("removing PFLAG_VPHYSICS_MOTIONCONTROLLER\n"); pPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, false ); if (bThrow) { Vector hitPosition; //TERO: take slightly more damage than regular melee CTakeDamageInfo info( this, this, sk_aliengrunt_dmg_claw.GetFloat() * 1.3f, DMG_BLAST ); pPlayer->VPhysicsTakeDamage( info ); hitPosition = ( pPlayer->WorldSpaceCenter() - WorldSpaceCenter()); hitPosition *= 12.0f; //GetVectors( &forward, NULL, NULL ); pPlayer->ViewPunch( QAngle(5,0,-18) ); pPlayer->VelocityPunch( hitPosition ); // Shake the screen UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START ); // Red damage indicator color32 red = { 128, 0, 0, 128 }; UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN ); } } } PhysEnableEntityCollisions( m_hPlayerConstrain->GetReferenceObject(), m_hPlayerConstrain->GetAttachedObject() ); PhysEnableEntityCollisions( VPhysicsGetObject(), m_hPlayerConstrain->GetAttachedObject() ); physenv->DestroyConstraint( m_hPlayerConstrain ); m_hPlayerConstrain = NULL; if (m_hRagdoll) { UTIL_Remove( m_hRagdoll ); m_hRagdoll = NULL; } return true; } return false; } void CNPC_AlienGrunt::GrabRagdoll() { if (GetEnemy()->Classify() == CLASS_PLAYER) { CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() ); if (pPlayer && !pPlayer->GetVehicle()) { IPhysicsObject *pPhysObject = pPlayer->VPhysicsGetObject(); Assert( pPhysObject ); Vector hitPosition = pPlayer->EyePosition(); int iAttachment = LookupAttachment( "righthand" ); //GetAttachment( iAttachment, hitPosition ); pPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, true ); //Create the thing where we constrain our player CBaseEntity *pBolt = CBaseEntity::CreateNoSpawn( "prop_dynamic", hitPosition, GetAbsAngles(), this ); pBolt->SetModelName( MAKE_STRING( "models/weapons/bee.mdl" ) ); pBolt->SetModel( "models/weapons/bee.mdl" ); DispatchSpawn( pBolt ); pBolt->AddEffects( EF_NODRAW ); pBolt->AddSolidFlags( FSOLID_NOT_SOLID ); pBolt->SetParent( this, iAttachment ); m_hRagdoll = pBolt; //Vector vecRight; //GetVectors(NULL, &vecRight, NULL ); IPhysicsObject *pPhys = pBolt->VPhysicsGetObject(); constraint_hingeparams_t hingeParams; hingeParams.Defaults(); hingeParams.worldAxisDirection = Vector(0,0,1); //vecRight; hingeParams.worldPosition = hitPosition; m_hPlayerConstrain = physenv->CreateHingeConstraint( pPhys, pPhysObject, NULL, hingeParams ); //TERO: disable collisions between the "bolt" and the player PhysDisableEntityCollisions( pPhysObject, pPhys ); PhysDisableEntityCollisions( VPhysicsGetObject(), pPhys ); m_flNextRagdollGrabTime = gpGlobals->curtime + aliengrunt_ragdoll_grab_delay.GetInt(); RemoveSpawnFlags( SF_ALIENGRUNT_WAIT_FOR_GRAB ); } } else { CStudioHdr *pStudioHdr = GetModelPtr(); int boneIndex = Studio_BoneIndexByName( pStudioHdr, pFollowerBoneNames[0] ); CBaseEntity *pEnemy = GetEnemy(); CAI_BaseNPC *pNPC = pEnemy ? pEnemy->MyNPCPointer() : NULL; bool bIsValidTarget = pNPC && pNPC->GetModelPtr(); if (!bIsValidTarget) return; if (pNPC->Classify() != CLASS_COMBINE && pNPC->Classify() != CLASS_METROPOLICE && pNPC->Classify() != CLASS_PLAYER_ALLY_VITAL ) { return; } pStudioHdr = pNPC->GetModelPtr(); int parentBoneIndex = Studio_BoneIndexByName( pStudioHdr, pFollowerBoneNames[1] ); Vector hitPosition; GetAttachment( "righthand", hitPosition ); Vector localHit = Vector(26,0,0); //TERO: in the model it's 25 Vector delta; VectorSubtract( pEnemy->GetAbsOrigin(), hitPosition, delta ); if ( delta.LengthSqr() > (ALIENGRUNT_GRAB_RANGE * ALIENGRUNT_GRAB_RANGE) ) return; Vector vecStabPos = ( pEnemy->WorldSpaceCenter() + Vector(0,0,35) + pEnemy->EyePosition() ) * 0.5f; CTakeDamageInfo damageInfo( this, this, 100, DMG_CRUSH ); Vector forward; pEnemy->GetVectors( &forward, NULL, NULL ); damageInfo.SetDamagePosition( hitPosition ); damageInfo.SetDamageForce( Vector(0,0,0) ); //-50 * 300 * forward ); pEnemy->TakeDamage( damageInfo ); if ( !pNPC || pNPC->IsAlive() ) return; Vector vecBloodDelta = hitPosition - vecStabPos; vecBloodDelta.z = 0; // effect looks better VectorNormalize( vecBloodDelta ); UTIL_BloodSpray( vecStabPos + vecBloodDelta * 4, vecBloodDelta, BLOOD_COLOR_RED, 8, FX_BLOODSPRAY_ALL ); UTIL_BloodSpray( vecStabPos + vecBloodDelta * 4, vecBloodDelta, BLOOD_COLOR_RED, 11, FX_BLOODSPRAY_DROPS ); CBaseEntity *pRagdoll = CreateServerRagdollAttached( pNPC, vec3_origin, -1, COLLISION_GROUP_DEBRIS, VPhysicsGetObject(), this, boneIndex, vecStabPos, parentBoneIndex, localHit ); if ( pRagdoll ) { CRagdollProp *pPropRagdoll = dynamic_cast( pRagdoll); if (pPropRagdoll) { ragdoll_t *pRagdollPhys = pPropRagdoll->GetRagdoll( ); Vector vSpeed(0,0,0); AngularImpulse aImpulse(0,0,0); float flSpeed = 6.0f; int j; for ( j = 0; j < pRagdollPhys->listCount; ++j ) { pRagdollPhys->list[j].pObject->SetDamping(&flSpeed, &flSpeed); pRagdollPhys->list[j].pObject->SetVelocityInstantaneous(&vSpeed,&aImpulse); } } m_hRagdoll = pRagdoll; m_flNextRagdollGrabTime = gpGlobals->curtime + aliengrunt_ragdoll_grab_delay.GetInt(); RemoveSpawnFlags( SF_ALIENGRUNT_WAIT_FOR_GRAB ); UTIL_Remove( pNPC ); } } } void CNPC_AlienGrunt::ThrowRagdoll() { if (!DestroyPlayerConstrain(true)) { CBaseEntity *pEntity = m_hRagdoll; CRagdollProp *pRagdoll = dynamic_cast( pEntity); if ( pRagdoll ) { //CPASAttenuationFilter filter( pRagdoll, "NPC_Strider.RagdollDetach" ); //EmitSound( filter, pRagdoll->entindex(), "NPC_Strider.RagdollDetach" ); DetachAttachedRagdoll( pRagdoll ); PhysDisableEntityCollisions( pEntity, this ); Vector vVel; QAngle aVel; AngularImpulse aImp(0,0,0); //GetVectors(&vVel, &vRig, NULL); GetAttachment( "righthand", vVel, aVel ); AngleVectors(aVel, &vVel ); //vVel = ( 0.75 * vVel) + (0.25 * vRig); aImp.x = -aVel.x * 10; aImp.y = -aVel.y * 10; vVel.z = 1; vVel *= 800; //250 ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( ); float flSpeed = 1.0f; pRagdoll->SetAbsVelocity(vVel); int j; for ( j = 0; j < pRagdollPhys->listCount; ++j ) { pRagdollPhys->list[j].pObject->SetDamping(&flSpeed, &flSpeed); pRagdollPhys->list[j].pObject->SetVelocity( &vVel, &aImp ); } } } ClearCondition( COND_ALIENGRUNT_CAN_GRAB_RAGDOLL ); m_hRagdoll = NULL; } /*int CNPC_AlienGrunt::MeleeAttack2Conditions ( float flDot, float flDist ) { DevMsg("Grab ragdoll Conditions\n"); if (!HasSpawnFlags( SF_ALIENGRUNT_CAN_GRAB_RAGDOLL)) return COND_NONE; if (GetEnemy() && GetEnemy()->Classify() != CLASS_COMBINE && GetEnemy()->Classify() != CLASS_METROPOLICE) { DevMsg("Not right enemy\n"); return COND_NONE; } if (flDist > 50 ) { DevMsg("Too far\n"); return COND_TOO_FAR_TO_ATTACK; } if (flDot < 0.7) { DevMsg("Not facing enemy\n"); ClearCondition(COND_TOO_FAR_TO_ATTACK); return COND_NOT_FACING_ATTACK; } DevMsg("COND_CAN_GRAB_RAGDOLL\n"); // Move around some more return COND_ALIENGRUNT_CAN_GRAB_RAGDOLL; }*/ void CNPC_AlienGrunt::NPCThink( void ) { /*if (HasSpawnFlags( SF_ALIENGRUNT_CAN_GRAB_RAGDOLL )) { m_BoneFollowerManager.UpdateBoneFollowers(this); }*/ BaseClass::NPCThink(); } #endif //----------------------------------------------------------------------------- // Purpose: // Input : *pEntity - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) { CBaseEntity *pHitEntity = NULL; if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) ) return true; // If we hit something that's okay to hit anyway, still fire if ( pHitEntity && pHitEntity->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS ) { if ( pHitEntity->m_takedamage == DAMAGE_YES ) return true; } /*if (pHitEntity && pHitEntity->Classify() == CLASS_COMBINE_MAIN_FRAME) { return true; }*/ if (ppBlocker) { *ppBlocker = pHitEntity; } return false; } Activity CNPC_AlienGrunt::TranslateAimingActivity(Activity iActivity) { m_bShouldAim = false; if (GetEnemy() && HasCondition( COND_SEE_ENEMY) ) { if (iActivity == ACT_RUN) { return ACT_RUN_AIM; } else if (iActivity == ACT_WALK) { return ACT_WALK_AIM; } else if (iActivity == ACT_IDLE) { return ACT_IDLE_AIM_AGITATED; } if ( iActivity == ACT_RUN_AIM || iActivity == ACT_WALK_AIM || iActivity == ACT_IDLE_AIM_AGITATED ) { m_bShouldAim = true; } } else { if (iActivity == ACT_RUN_AIM) { return ACT_RUN; } else if (iActivity == ACT_WALK_AIM) { return ACT_WALK; } else if (iActivity == ACT_IDLE_AIM_AGITATED) { return ACT_IDLE; } } return iActivity; } void CNPC_AlienGrunt::PrescheduleThink(void) { BaseClass::PrescheduleThink(); //TERO: moved here if (!HasCondition( COND_CAN_MELEE_ATTACK1 ) && !HasCondition( COND_CAN_MELEE_ATTACK2 ) && !HasCondition( COND_ALIENGRUNT_SHOULD_PROTECT_SQUADMATE ) && (HasCondition( COND_CAN_RANGE_ATTACK1 ) || HasCondition( COND_ALIENGRUNT_BEES ))&& !IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) ) { //DevMsg("ACT_GESTURE_RANGE_ATTACK1\n"); ShootBees(); RestartGesture( ACT_GESTURE_RANGE_ATTACK1, true ); ClearCondition( COND_CAN_RANGE_ATTACK1 ); ClearCondition( COND_ALIENGRUNT_BEES ); } if ( m_hRagdoll == NULL && m_hPlayerConstrain == NULL && (IsCurSchedule( SCHED_ALIENGRUNT_GRAB_RAGDOLL ) || HasCondition( COND_ALIENGRUNT_CAN_GRAB_RAGDOLL ) )) { bool bFailed = false; if (HasCondition( COND_ENEMY_DEAD ) || HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_NEW_ENEMY )) { bFailed = true; } else if (GetEnemy()) { if (GetEnemy()->MyCombatCharacterPointer() && GetEnemy()->MyCombatCharacterPointer()->IsInAVehicle()) { bFailed = true; } else { Vector vecEnemy, vecForward; GetVectors(&vecForward, NULL, NULL); vecEnemy = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); vecEnemy.z = 0; float flDist = VectorNormalize(vecEnemy); float flDot = DotProduct(vecEnemy, vecForward); if (flDist > 60 || flDot < 0.6) { //DevMsg("Cancelled ragdoll grabbing, dist %f, dot product %f \n", flDist, flDot); //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vecForward, 255, 0, 0, true, 0.1f ); //NDebugOverlay::Line( GetEnemy()GetAbsOrigin(), GetAbsOrigin() + vecForward, 255, 0, 0, true, 0.1f ); bFailed = true; } } } else { bFailed = true; } if (bFailed) { SetCondition( COND_ALIENGRUNT_CANNOT_GRAB_RAGDOLL ); ClearCondition( COND_ALIENGRUNT_CAN_GRAB_RAGDOLL ); m_flNextRagdollGrabTime = gpGlobals->curtime + 1.0f + aliengrunt_ragdoll_grab_delay.GetInt(); } } int iActivity = GetActivity(); int iNewActivity = TranslateAimingActivity( (Activity) iActivity ); if ( iActivity != iNewActivity ) { SetActivity( (Activity) iNewActivity ); GetNavigator()->SetMovementActivity( (Activity) iNewActivity ); } UpdateHead(); UpdateBeehiveGunAim(); if ( ( m_flBurnDamageResetTime ) && ( gpGlobals->curtime >= m_flBurnDamageResetTime ) ) { m_flBurnDamage = 0; } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- inline bool CNPC_AlienGrunt::InAttackSequence( void ) { if ( m_MoveAndShootOverlay.IsMovingAndShooting() ) return true; if ( GetActivity() == ACT_RANGE_ATTACK1 ) return true; if ( IsPlayingGesture( ACT_GESTURE_RANGE_ATTACK1 ) ) return true; return false; } //TERO: YOU COULD OPTIMIZE BY PUTTING THESE TWO IN A SAME FUNCTION void CNPC_AlienGrunt::UpdateBeehiveGunAim(void) { float yaw = GetPoseParameter( "aim_yaw" ); float pitch = GetPoseParameter( "aim_pitch" ); // If we should be watching our enemy, turn our head if ( m_bShouldAim && ( GetEnemy() != NULL ) ) { Vector vecEnemy; GetEnemy()->BodyTarget(vecEnemy); Vector enemyDir = vecEnemy - WorldSpaceCenter(); VectorNormalize( enemyDir ); float angle = VecToYaw( BodyDirection3D() ); float angleDiff = VecToYaw( enemyDir ); angleDiff = UTIL_AngleDiff( angleDiff, angle + yaw ); SetPoseParameter( "aim_yaw", UTIL_Approach( yaw + angleDiff, yaw, 50 ) ); angle = UTIL_VecToPitch( BodyDirection3D() ); angleDiff = UTIL_VecToPitch( enemyDir ); angleDiff = UTIL_AngleDiff( angleDiff, angle + pitch ); SetPoseParameter( "aim_pitch", UTIL_Approach( pitch + angleDiff, pitch, 50 ) ); } else { // Otherwise turn the head back to its normal position SetPoseParameter( "aim_yaw", UTIL_Approach( 0, yaw, 10 ) ); SetPoseParameter( "aim_pitch", UTIL_Approach( 0, pitch, 10 ) ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AlienGrunt::UpdateHead( void ) { float yaw = GetPoseParameter( "head_yaw" ); float pitch = GetPoseParameter( "head_pitch" ); int nActivity = GetActivity(); // If we should be watching our enemy, turn our head if ( ( GetEnemy() != NULL ) && ( nActivity != ACT_ALIENGRUNT_CHARGE_START ) && ( nActivity != ACT_ALIENGRUNT_CHARGE_HIT ) && ( nActivity != ACT_ALIENGRUNT_CHARGE_CANCEL ) && ( nActivity != ACT_ALIENGRUNT_CHARGE_LOOP ) ) { Vector enemyDir = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter(); VectorNormalize( enemyDir ); float angle = VecToYaw( BodyDirection3D() ); float angleDiff = VecToYaw( enemyDir ); angleDiff = UTIL_AngleDiff( angleDiff, angle + yaw ); SetPoseParameter( "head_yaw", UTIL_Approach( yaw + angleDiff, yaw, 50 ) ); if (GetActivity() == ACT_ALIENGRUNT_CHARGE_LOOP) { SetPoseParameter( "head_pitch", UTIL_Approach( 43.6, pitch, 50 ) ); } else { angle = UTIL_VecToPitch( BodyDirection3D() ); angleDiff = UTIL_VecToPitch( enemyDir ); angleDiff = UTIL_AngleDiff( angleDiff, angle + pitch ); SetPoseParameter( "head_pitch", UTIL_Approach( pitch + angleDiff, pitch, 50 ) ); } } else { // Otherwise turn the head back to its normal position SetPoseParameter( "head_yaw", UTIL_Approach( 0, yaw, 10 ) ); SetPoseParameter( "head_pitch", UTIL_Approach( 0, pitch, 10 ) ); } } bool CNPC_AlienGrunt::CreateBehaviors() { AddBehavior( &m_AssaultBehavior ); //AddBehavior( &m_LeadBehavior ); //AddBehavior( &m_FollowBehavior ); return BaseClass::CreateBehaviors(); } float CNPC_AlienGrunt::MaxYawSpeed( void ) { Activity eActivity = GetActivity(); if ( (eActivity == ACT_ALIENGRUNT_TO_ACTION) || (eActivity == ACT_ALIENGRUNT_TO_IDLE) ) return 0.0f; //TERO: note that these have to be checked outside of switch/case if ( eActivity == ACT_ALIENGRUNT_CHARGE_START ) return 4.0f; if ( eActivity == ACT_ALIENGRUNT_CHARGE_LOOP ) { if ( !GetEnemy() ) return 2.0f; float dist = UTIL_DistApprox2D( GetEnemy()->WorldSpaceCenter(), WorldSpaceCenter() ); if ( dist > 128 ) return 24.0f; //FIXME: Alter by skill level float yawSpeed = RemapValClamped( dist, 0, 128, 1.0f, 2.0f ); //yawSpeed = clamp( yawSpeed, 2.0f, 4.0f ); return (yawSpeed * RemapValClamped( m_iHealth, 20, m_iMaxHealth, 12.0f, 6.0f)); } if ( eActivity == ACT_ALIENGRUNT_CHARGE_CANCEL ) return 8.0f; switch( eActivity ) { // Stay still case ACT_RANGE_ATTACK1: case ACT_MELEE_ATTACK1: return 0.0f; break; case ACT_IDLE: return 30.0f; break; case ACT_TURN_LEFT: case ACT_TURN_RIGHT: return 40.0f; break; case ACT_RUN: default: return 30.0f; break; } } void CNPC_AlienGrunt::StartTask( const Task_t *pTask ) { switch ( pTask->iTask) { case TASK_ALIENGRUNT_CHASE_ENEMY_CONTINUOUSLY: StartTaskChaseEnemyContinuously( pTask ); break; case TASK_ALIENGRUNT_SHOOT_BEES: { SetIdealActivity( ACT_RANGE_ATTACK1 ); } break; case TASK_ALIENGRUNT_GET_PATH_TO_PHYSOBJ: { CBaseEntity *pPhysicsEntity = m_hPhysicsEnt; if( !pPhysicsEntity ) { TaskFail("Physics ent NULL"); break; } Vector vecGoalPos; Vector vecDir; vecDir = GetLocalOrigin() - m_hPhysicsEnt->GetLocalOrigin(); VectorNormalize(vecDir); vecDir.z = 0; AI_NavGoal_t goal( m_hPhysicsEnt->WorldSpaceCenter() ); goal.pTarget = m_hPhysicsEnt; GetNavigator()->SetGoal( goal ); TaskComplete(); } break; case TASK_ALIENGRUNT_SWAT_ITEM: { if( m_hPhysicsEnt == NULL ) { // Physics Object is gone! Probably was an explosive // or something else broke it. TaskFail("Physics ent NULL"); } else if ( DistToPhysicsEnt() > ALIENGRUNT_PHYSOBJ_SWATDIST ) { // Physics ent is no longer in range! Probably another zombie swatted it or it moved // for some other reason. TaskFail( "Physics swat item has moved" ); } else { SetIdealActivity( (Activity)GetSwatActivity() ); } break; } break; case TASK_MELEE_ATTACK2: { AttackSound(); ResetIdealActivity( ACT_ALIENGRUNT_GRAB_RAGDOLL ); } break; case TASK_ALIENGRUNT_CHARGE: { // HACK: Because the guard stops running his normal blended movement // here, he also needs to remove his blended movement layers! GetMotor()->MoveStop(); SetActivity( ACT_ALIENGRUNT_CHARGE_START ); m_bDecidedNotToStop = false; ChargeSound(); m_flChargeCancelTime = gpGlobals->curtime + ALIENGRUNT_CHARGE_CANCEL_TIME; } break; case TASK_RANGE_ATTACK1: case TASK_MELEE_ATTACK1: AttackSound(); default: { BaseClass::StartTask( pTask ); break; } } } void CNPC_AlienGrunt::RunTask( const Task_t *pTask ) { /*if (GetCurSchedule()) DevMsg("npc_aliengrunt: running schedule with name: %s, with task id %d\n", GetCurSchedule()->GetName(), pTask->iTask ); else DevMsg("npc_aliengrunt: running schedule with task: %d\n", pTask->iTask );*/ switch ( pTask->iTask ) { case TASK_ALIENGRUNT_CHASE_ENEMY_CONTINUOUSLY: RunTaskChaseEnemyContinuously( pTask ); break; case TASK_ALIENGRUNT_SHOOT_BEES: { if ( IsWaitFinished() ) { //m_bShouldAim = false; TaskComplete(); } } break; case TASK_ALIENGRUNT_SWAT_ITEM: if( IsActivityFinished() ) { TaskComplete(); } break; case TASK_RANGE_ATTACK1: { if (GetEnemy() != NULL) { if (GetEnemy()->IsPlayer()) { m_flPlaybackRate = 1.5; } if (!GetEnemy()->IsAlive()) { if( IsActivityFinished() ) { TaskComplete(); break; } } // This is along attack sequence so if the enemy // becomes occluded bail /*if (HasCondition( COND_ENEMY_OCCLUDED )) { TaskComplete(); break; }*/ } BaseClass::RunTask( pTask ); break; } case TASK_MELEE_ATTACK1: { if (GetEnemy() == NULL) { if ( IsActivityFinished() ) { TaskComplete(); } } else { BaseClass::RunTask( pTask ); } break; } case TASK_MELEE_ATTACK2: { if ( IsActivityFinished() ) { TaskComplete(); } break; } case TASK_ALIENGRUNT_CHARGE: { Activity eActivity = GetActivity(); //DevMsg("npc_aliengrunt: running activity: %s\n", GetActivityName( eActivity )); //TERO: I am not sure what causes this to happen if ( eActivity == ACT_IDLE ) { SetActivity( ACT_ALIENGRUNT_CHARGE_START ); } // See if we're trying to stop after hitting/missing our target if ( eActivity == ACT_ALIENGRUNT_CHARGE_CANCEL || eActivity == ACT_ALIENGRUNT_CHARGE_HIT ) //ACT_ANTLIONGUARD_CHARGE_STOP || eActivity == ACT_ANTLIONGUARD_CHARGE_CRASH ) { if ( IsActivityFinished() ) { //DevMsg("npc_aliengrunt: ACT_ALIENGRUNT_CHARGE_CANCEL finished\n"); TaskComplete(); return; } // Still in the process of slowing down. Run movement until it's done. AutoMovement(); return; } // Check for manual transition if ( ( eActivity == ACT_ALIENGRUNT_CHARGE_START ) && ( IsActivityFinished() ) ) // ACT_ANTLIONGUARD_CHARGE_START { SetActivity( ACT_ALIENGRUNT_CHARGE_LOOP ); } // See if we're still running if ( eActivity == ACT_ALIENGRUNT_CHARGE_LOOP || eActivity == ACT_ALIENGRUNT_CHARGE_START ) { if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && !IsPlayingGesture(ACT_GESTURE_RANGE_ATTACK1) ) { RestartGesture( ACT_GESTURE_RANGE_ATTACK1 ); ShootBees(); ClearCondition( COND_CAN_RANGE_ATTACK1 ); //TERO: not sure if this is supposed to be done here } if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) ) { SetActivity( ACT_ALIENGRUNT_CHARGE_CANCEL ); return; } else { if ( GetEnemy() != NULL ) { Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); VectorNormalize( goalDir ); //TERO: the Charge Cancel time is added by me to avoid scenes where the Alien Grunt runs after the player in front // of him for long periods, which looks silly and makes the Alien Grunt vulnerable if ( DotProduct( BodyDirection2D(), goalDir ) < 0.25f || m_flChargeCancelTime < gpGlobals->curtime ) { if ( !m_bDecidedNotToStop ) { // We've missed the target. Randomly decide not to stop, which will cause // the guard to just try and swing around for another pass. m_bDecidedNotToStop = true; if ( RandomFloat(0,1) > 0.3 ) { //m_iChargeMisses++; SetActivity( ACT_ALIENGRUNT_CHARGE_CANCEL ); } m_flChargeCancelTime = gpGlobals->curtime + ALIENGRUNT_CHARGE_CANCEL_TIME; ChargeSound(); } } else { m_bDecidedNotToStop = false; } } } } // Steer towards our target float idealYaw; if ( GetEnemy() == NULL ) { idealYaw = GetMotor()->GetIdealYaw(); } else { idealYaw = CalcIdealYaw( GetEnemy()->GetAbsOrigin() ); } // Add in our steering offset idealYaw += ChargeSteer(); // Turn to face GetMotor()->SetIdealYawAndUpdate( idealYaw ); // See if we're going to run into anything soon ChargeLookAhead(); // Let our animations simply move us forward. Keep the result // of the movement so we know whether we've hit our target. AIMoveTrace_t moveTrace; if ( AutoMovement( GetEnemy(), &moveTrace ) == false ) { // Only stop if we hit the world if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) ) { // If we're starting up, this is an error if ( eActivity == ACT_ALIENGRUNT_CHARGE_START ) { TaskFail( "Unable to make initial movement of charge\n" ); return; } // Crash unless we're trying to stop already if ( eActivity != ACT_ALIENGRUNT_CHARGE_CANCEL ) { if ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin ) { SetActivity( ACT_ALIENGRUNT_CHARGE_CANCEL ); } else { SetActivity( ACT_ALIENGRUNT_CHARGE_HIT ); } } } else if ( moveTrace.pObstruction ) { // If we hit an antlion, don't stop, but kill it /*if ( moveTrace.pObstruction->Classify() == CLASS_ALIENGRUNT ) { if ( FClassnameIs( moveTrace.pObstruction, "npc_aliengrunt" ) ) { // Crash unless we're trying to stop already if ( eActivity != ACT_ALIENGRUNT_CHARGE_CANCEL ) { SetActivity( ACT_ALIENGRUNT_CHARGE_CANCEL ); } } }*/ //int disposition = CBaseCombatCharacter::GetDefaultRelationshipDispositionBetweenClasses( CLASS_ALIENGRUNT, moveTrace.pObstruction->Classify() ); if ( IRelationType( moveTrace.pObstruction ) == D_HT ) { // Crash unless we're trying to stop already if ( eActivity == ACT_ALIENGRUNT_CHARGE_LOOP ) { SetActivity( ACT_ALIENGRUNT_CHARGE_HIT ); //ChargeDamage( pEntity ); ChargeDamage( moveTrace.pObstruction ); moveTrace.pObstruction->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 50 ) ); } } else if ( IRelationType( moveTrace.pObstruction ) == D_LI ) { // Crash unless we're trying to stop already if ( eActivity == ACT_ALIENGRUNT_CHARGE_LOOP ) { SetActivity( ACT_ALIENGRUNT_CHARGE_CANCEL ); } } else { //TERO: this reduces the cancel time so that if we have had impact shock recently m_flChargeCancelTime = m_flChargeCancelTime - 1.0f; //DevMsg("Cancel in %f\n", m_flChargeCancelTime - gpGlobals->curtime); } } } } break; default: { BaseClass::RunTask( pTask ); break; } } } int CNPC_AlienGrunt::GetSoundInterests ( void) { return SOUND_WORLD | SOUND_COMBAT | SOUND_CARCASS | SOUND_MEAT | SOUND_GARBAGE | SOUND_DANGER | SOUND_PLAYER; } //----------------------------------------------------------------------------- // Purpose: // Input : // Output : //----------------------------------------------------------------------------- Class_T CNPC_AlienGrunt::Classify ( void ) { return CLASS_ALIENGRUNT; } int CNPC_AlienGrunt::RangeAttack1Conditions( float flDot, float flDist ) { if (GetEnemy() == NULL) { return( COND_NONE ); } if ( gpGlobals->curtime < m_flNextAttack || HasSpawnFlags(SF_ALIENGRUNT_WAIT_FOR_GRAB) ) { return( COND_NONE ); } // Range attack is ineffective on manhack so never use it // Melee attack looks a lot better anyway /*if (GetEnemy()->Classify() == CLASS_MANHACK) { return( COND_NONE ); }*/ /*if ( flDist <= ALIENGRUNT_TOO_CLOSE_FOR_BEES ) { DevMsg("AlienGrunt: too close for bees\n"); return( COND_TOO_CLOSE_TO_ATTACK ); }*/ if ( flDist > 1500 * 12 ) // 1500ft max { //DevMsg("npc_aliengrunt: too far %f\n", flDist); return( COND_TOO_FAR_TO_ATTACK ); } else if ( flDot < 0.65 ) { return( COND_NOT_FACING_ATTACK ); } return COND_ALIENGRUNT_BEES; //( COND_CAN_RANGE_ATTACK1 ); } //----------------------------------------------------------------------------- // Purpose: For innate melee attack // Input : // Output : //----------------------------------------------------------------------------- int CNPC_AlienGrunt::MeleeAttack1Conditions ( float flDot, float flDist ) { /*float flMax = 50; if (GetEnemy() && GetEnemy()->Classify() == CLASS_COMBINE_MAIN_FRAME) { flMax = 160.0f; }*/ if (flDist > 50 ) { return COND_TOO_FAR_TO_ATTACK; } if (flDot < 0.7) { //ClearCondition(COND_TOO_FAR_TO_ATTACK); return COND_NOT_FACING_ATTACK; } /*if ( ( HasSpawnFlags( SF_ALIENGRUNT_CAN_GRAB_RAGDOLL ) ) && ( m_flNextRagdollGrabTime < gpGlobals->curtime ) && GetEnemy() ) { switch (GetEnemy()->Classify()) { case CLASS_PLAYER_ALLY_VITAL: { if (GetEnemy()->m_iHealth > 10) { break; } } case CLASS_COMBINE: case CLASS_METROPOLICE: { if (FClassnameIs(GetEnemy(), "npc_combine_s") || FClassnameIs(GetEnemy(), "npc_metropolice" ) || FClassnameIs(GetEnemy(), "npc_noah" ) || FClassnameIs(GetEnemy(), "npc_eloise" ) || FClassnameIs(GetEnemy(), "npc_larson" )) { return COND_ALIENGRUNT_CAN_GRAB_RAGDOLL; } } break; case CLASS_PLAYER: { CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() ); if (pPlayer && !pPlayer->GetVehicle()) { return COND_ALIENGRUNT_CAN_GRAB_RAGDOLL; } } break; } }*/ // Build a cube-shaped hull, the same hull that ClawAttack() is going to use. Vector vecMins = GetHullMins(); Vector vecMaxs = GetHullMaxs(); vecMins.z = vecMins.x; vecMaxs.z = vecMaxs.x; Vector forward; GetVectors( &forward, NULL, NULL ); trace_t tr; CTraceFilterNav traceFilter( this, false, this, COLLISION_GROUP_NONE ); AI_TraceHull( WorldSpaceCenter(), WorldSpaceCenter() + forward * 50, vecMins, vecMaxs, MASK_NPCSOLID, &traceFilter, &tr ); if( tr.fraction == 1.0 || !tr.m_pEnt ) { // This attack would miss completely. Trick the zombie into moving around some more. return COND_TOO_FAR_TO_ATTACK; } /*if (tr.m_pEnt->Classify() == CLASS_COMBINE_MAIN_FRAME ) { return COND_CAN_MELEE_ATTACK1; }*/ if( tr.m_pEnt == GetEnemy() || tr.m_pEnt->IsNPC() || (tr.m_pEnt->m_takedamage == DAMAGE_YES && (dynamic_cast(tr.m_pEnt))) ) { // -Let the zombie swipe at his enemy if he's going to hit them. // -Also let him swipe at NPC's that happen to be between the zombie and the enemy. // This makes mobs of zombies seem more rowdy since it doesn't leave guys in the back row standing around. // -Also let him swipe at things that takedamage, under the assumptions that they can be broken. return COND_CAN_MELEE_ATTACK1; } if( tr.m_pEnt->IsBSPModel() ) { // The trace hit something solid, but it's not the enemy. If this item is closer to the zombie than // the enemy is, treat this as an obstruction. Vector vecToEnemy = GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter(); Vector vecTrace = tr.endpos - tr.startpos; if( vecTrace.Length2DSqr() < vecToEnemy.Length2DSqr() ) { return COND_ALIENGRUNT_LOCAL_MELEE_OBSTRUCTION; } } if ( !tr.m_pEnt->IsWorld() && GetEnemy() && GetEnemy()->GetGroundEntity() == tr.m_pEnt ) { //Try to swat whatever the player is standing on instead of acting like a dill. return COND_CAN_MELEE_ATTACK1; } // Move around some more return COND_TOO_FAR_TO_ATTACK; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- static bool IsMovablePhysicsObject( CBaseEntity *pEntity ) { return pEntity && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->IsMoveable(); } void CNPC_AlienGrunt::Claw( int iAttachment) { CBaseEntity *pHurt = CheckTraceHullAttack( 50, Vector(-10,-10,-10), Vector(10,10,10),sk_aliengrunt_dmg_claw.GetFloat(), DMG_SLASH ); if ( pHurt ) { // Play a random attack hit sound EmitSound( "NPC_Vortigaunt.Claw" ); CBasePlayer *pPlayer = ToBasePlayer( pHurt ); if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE ) ) { Vector vecOrigin; //, forward; QAngle angles; GetAttachment( iAttachment, vecOrigin, angles ); vecOrigin = ( pPlayer->WorldSpaceCenter() - vecOrigin); vecOrigin *= 2.0f; //GetVectors( &forward, NULL, NULL ); pPlayer->ViewPunch( QAngle(5,0,-18) ); pPlayer->VelocityPunch( vecOrigin ); // Shake the screen UTIL_ScreenShake( pPlayer->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START ); // Red damage indicator color32 red = { 128, 0, 0, 128 }; UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN ); } else if ( !pPlayer ) { if ( IsMovablePhysicsObject( pHurt ) ) { // If it's a vphysics object that's too heavy, crash into it too. IPhysicsObject *pPhysics = pHurt->VPhysicsGetObject(); if ( pPhysics ) { // If the object is being held by the player, break it or make them drop it. if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) { // If it's breakable, break it. if ( pHurt->m_takedamage == DAMAGE_YES ) { CBreakableProp *pBreak = dynamic_cast(pHurt); if ( pBreak ) { CTakeDamageInfo info( this, this, 20, DMG_SLASH ); pBreak->Break( this, info ); } } } } } } } } //----------------------------------------------------------------------------- // Purpose: Called when we are trying to open a prop_door and it's time to start // the door moving. This is called either in response to an anim event // or as a fallback when we don't have an appropriate open activity. //----------------------------------------------------------------------------- void CNPC_AlienGrunt::OpenPropDoorNow( CBasePropDoor *pDoor ) { //TERO: we accept the door if we are not going for a physics entity or we already have a door if ( (m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT )&& pDoor->HasSpawnFlags(SF_BREAKABLE_BY_NPCS)) { m_hDoor = pDoor; //DevMsg("npc_aliengrunt: a breakable door during OpenPropDoornow(), grunt smash!\n"); return; } // Start the door moving. pDoor->NPCOpenDoor(this); // Wait for the door to finish opening before trying to move through the doorway. m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval(); } //TERO: The swat AE_ copied from the Base Zombie void CNPC_AlienGrunt::SwatItem() { Vector v; CBaseEntity *pPhysicsEntity = m_hPhysicsEnt; if( !pPhysicsEntity ) { DevWarning( "**Alie Grunt: Missing my physics ent!!" ); m_hPhysicsEnt = NULL; return; } CBasePropDoor *pDoor = dynamic_cast((CBaseEntity*)m_hPhysicsEnt); if (pDoor) { // add some spin so the object doesn't appear to just fly in a straight line // Also this spin will move the object slightly as it will press on whatever the object // is resting on. AngularImpulse angVelocity( random->RandomFloat(-180, 180), 20, random->RandomFloat(-360, 360) ); ClearCondition( COND_ALIENGRUNT_CAN_ATTACK_DOOR ); m_hDoor = NULL; pDoor->BreakDoors(WorldSpaceCenter(), angVelocity); EmitSound("d1_trainstation_03.breakin_doorkick"); return; } IPhysicsObject *pPhysObj = pPhysicsEntity->VPhysicsGetObject(); if( !pPhysObj ) { DevWarning( "**Alien Grunt: No Physics Object for physics Ent!" ); return; } CBaseEntity *pEnemy = GetEnemy(); if (m_hSetTarget == m_hPhysicsEnt) { pEnemy = m_hSetTarget; m_hSetTarget = NULL; } if ( pEnemy ) { EmitSound( "NPC_Vortigaunt.Claw" ); PhysicsImpactSound( pEnemy, pPhysObj, CHAN_BODY, pPhysObj->GetMaterialIndex(), physprops->GetSurfaceIndex("flesh"), 0.5, 800 ); Vector physicsCenter = pPhysicsEntity->WorldSpaceCenter(); v = pEnemy->WorldSpaceCenter() - physicsCenter; VectorNormalize(v); // Send the object at 800 in/sec toward the enemy. Add 200 in/sec up velocity to keep it // in the air for a second or so. v = v * 1000; v.z += 200; // add some spin so the object doesn't appear to just fly in a straight line // Also this spin will move the object slightly as it will press on whatever the object // is resting on. AngularImpulse angVelocity( random->RandomFloat(-180, 180), 20, random->RandomFloat(-360, 360) ); pPhysObj->AddVelocity( &v, &angVelocity ); // If we don't put the object scan time well into the future, the zombie // will re-select the object he just hit as it is flying away from him. // It will likely always be the nearest object because the zombie moved // close enough to it to hit it. m_hPhysicsEnt = NULL; m_flNextSwatScan = gpGlobals->curtime + ALIENGRUNT_SWAT_DELAY; return; } } void CNPC_AlienGrunt::HandleAnimEvent( animevent_t *pEvent ) { if ( pEvent->event == AE_ALIENGRUNT_HAND_LEFT ) { if (IsCurSchedule( SCHED_ALIENGRUNT_SWATITEM) || IsCurSchedule(SCHED_ALIENGRUNT_MOVE_SWATITEM) || IsCurSchedule(SCHED_ALIENGRUNT_ATTACKITEM) ) SwatItem(); else Claw( m_iLeftHandAttachment ); return; } if ( pEvent->event == AE_ALIENGRUNT_HAND_RIGHT ) { if (IsCurSchedule( SCHED_ALIENGRUNT_SWATITEM) || IsCurSchedule(SCHED_ALIENGRUNT_MOVE_SWATITEM) || IsCurSchedule(SCHED_ALIENGRUNT_ATTACKITEM) ) SwatItem(); else Claw( m_iRightHandAttachment ); return; } if ( pEvent->event == AE_ALIENGRUNT_SHOOTBEES ) { ShootBees(); /*CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, this, SOUNDENT_CHANNEL_WEAPON, GetEnemy() ); EmitSound( "NPC_AlienGrunt.Shoot" ); m_iBees--; if (m_iBees <= 0) { m_flNextAttack = gpGlobals->curtime + ALIENGRUNT_NEXT_ATTACK_DELAY; if (m_iHealth < 25) { m_iBees = ALIENGRUNT_BEE_BURST_SIZE_MAX; } else { m_iBees = ALIENGRUNT_BEE_BURST_SIZE_MIN; } } else { m_flNextAttack = gpGlobals->curtime + ALIENGRUNT_NEXT_ATTACK_DELAY_SMALL; }*/ return; } #ifdef ALIENGRUNT_CUSTOM_FOOT_AE if ( pEvent->event == AE_ALIENGRUNT_FOOT_LEFT ) #else if ( pEvent->event == AE_NPC_LEFTFOOT ) #endif { if (GetWaterLevel() != WL_NotInWater) { EmitSound("Water.StepLeft", pEvent->eventtime ); } else { EmitSound( "NPC_Vortigaunt.FootstepLeft", pEvent->eventtime ); } return; } #ifdef ALIENGRUNT_CUSTOM_FOOT_AE if ( pEvent->event == AE_ALIENGRUNT_FOOT_RIGHT ) #else if ( pEvent->event == AE_NPC_RIGHTFOOT ) #endif { if (GetWaterLevel() != WL_NotInWater) { EmitSound("Water.StepRight", pEvent->eventtime ); } else { EmitSound( "NPC_Vortigaunt.FootstepRight", pEvent->eventtime ); } return; } if ( pEvent->event == AE_ALIENGRUNT_SWING_SOUND ) { EmitSound( "NPC_Vortigaunt.Swing" ); return; } if ( pEvent->event == AE_ALIENGRUNT_GRAB_RAGDOLL ) { //DevMsg("Grabbing a ragdoll\n"); GrabRagdoll(); return; } if ( pEvent->event == AE_ALIENGRUNT_THROW_RAGDOLL) { //DevMsg("Throwing a ragdoll\n"); ThrowRagdoll(); return; } if ( pEvent->event == AE_ALIENGRUNT_CHARGE_HIT ) { UTIL_ScreenShake( GetAbsOrigin(), 32.0f, 4.0f, 1.0f, 512, SHAKE_START ); EmitSound( "NPC_Vortigaunt.Claw" ); Vector startPos = GetAbsOrigin(); float checkSize = ( CollisionProp()->BoundingRadius() + 8.0f ); Vector endPos = startPos + ( BodyDirection3D() * checkSize ); CTraceFilterChargeAlienGrunt traceFilter( this, COLLISION_GROUP_NONE, this ); Ray_t ray; ray.Init( startPos, endPos, GetHullMins(), GetHullMaxs() ); trace_t tr; enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr ); // Cause a shock wave from this point which will distrupt nearby physics objects ImpactShock( tr.endpos, 100, 30 ); return; } BaseClass::HandleAnimEvent( pEvent ); } Activity CNPC_AlienGrunt::NPC_TranslateActivity( Activity eNewActivity ) { /*if (eNewActivity == ACT_ALIENGRUNT_TO_ACTION || eNewActivity == ACT_RANGE_ATTACK1 ) { m_bShouldAim = true; } else { m_bShouldAim = false; }*/ //TERO: If the Alien Grunt is close enough to his enemy, he will walk to him instead of running /*if (eNewActivity == ACT_RUN) //Commented out because it doesn't look that great { if (GetEnemy()!=NULL) { float flDist=UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin()); if (flDistEyePosition().z - GetLocalOrigin().z) < 20) { return ACT_MELEE_ATTACK2; } } return BaseClass::NPC_TranslateActivity( eNewActivity ); } //------------------------------------------------------------------------------ // Purpose : If I've been in alert for a while and nothing's happened, // go back to idle // Input : // Output : //------------------------------------------------------------------------------ bool CNPC_AlienGrunt::ShouldGoToIdleState( void ) { if (m_flLastStateChangeTime + 10 < gpGlobals->curtime) { return true; } return false; } void CNPC_AlienGrunt::Spawn() { Precache(); SetModel("models/aliengrunt.mdl"); BaseClass::Spawn(); SetHullType(HULL_WIDE_HUMAN); SetHullSizeNormal(); SetNavType( NAV_GROUND ); SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetMoveType( MOVETYPE_STEP ); m_bloodColor = BLOOD_COLOR_GREEN; m_iHealth = sk_aliengrunt_health.GetFloat(); m_iMaxHealth = m_iHealth; m_flFieldOfView = VIEW_FIELD_NARROW; // NOTE: we need a wide field of view so npc will notice player and say hello m_NPCState = NPC_STATE_NONE; //TERO: we can't dissolve with full healths AddEFlags( EFL_NO_DISSOLVE ); GetExpresser()->SetVoicePitch( random->RandomInt( 85, 110 ) ); CapabilitiesClear(); CapabilitiesAdd( bits_CAP_SQUAD ); CapabilitiesAdd ( bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD | bits_CAP_MOVE_GROUND ); CapabilitiesAdd ( bits_CAP_INNATE_RANGE_ATTACK1 ); CapabilitiesAdd ( bits_CAP_MOVE_SHOOT ); CapabilitiesAdd ( bits_CAP_AIM_GUN ); CapabilitiesAdd ( bits_CAP_INNATE_MELEE_ATTACK1 ); CapabilitiesAdd ( bits_CAP_DOORS_GROUP ); CapabilitiesAdd ( bits_CAP_NO_HIT_SQUADMATES | bits_CAP_FRIENDLY_DMG_IMMUNE ); //CapabilitiesAdd ( bits_CAP_MOVE_FLY ); //TERO: this may have to be here for the bee's capabilities check m_flEyeIntegRate = 0.6; // Got a big eyeball so turn it slower //m_bStopLoopingSounds = false; m_bShouldAim = false; m_iLeftHandAttachment = LookupAttachment( ALIENGRUNT_LEFT_HAND ); m_iRightHandAttachment = LookupAttachment( ALIENGRUNT_RIGHT_HAND ); NPCInit(); m_HideSoundTime = 0; #ifdef ALIENGRUNT_ALWAYS_RAGDOLL_GRAB if (aliengrunt_always_ragdoll_grab.GetBool()) { AddSpawnFlags( SF_ALIENGRUNT_CAN_GRAB_RAGDOLL ); } #endif m_hRagdoll = NULL; m_flNextRagdollGrabTime = 0; m_hPlayerConstrain = NULL; m_hPhysicsEnt = NULL; m_hObstructor = NULL; m_hSetTarget = NULL; m_hDoor = NULL; m_flNextChargeTime = gpGlobals->curtime; // + aliengrunt_charge_delay_max.GetFloat(); m_flChargeCancelTime = 0; m_iBees = ALIENGRUNT_BEE_BURST_SIZE_MIN; // Don't allow us to skip animation setup because our attachments are critical to us! SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP ); m_flBurnDamageResetTime = 0; m_flBurnDamage = 0; } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CNPC_AlienGrunt::Precache() { PrecacheModel( "models/aliengrunt.mdl" ); UTIL_PrecacheOther( "bee_missile" ); TalkInit(); PrecacheScriptSound( "NPC_AlienGrunt.Idle" ); PrecacheScriptSound( "NPC_AlienGrunt.Hide1" ); PrecacheScriptSound( "NPC_AlienGrunt.Hide2" ); PrecacheScriptSound( "NPC_AlienGrunt.Hide3" ); PrecacheScriptSound( "NPC_AlienGrunt.Hide4" ); PrecacheScriptSound( "NPC_AlienGrunt.Alert" ); PrecacheScriptSound( "NPC_AlienGrunt.Combine" ); PrecacheScriptSound( "NPC_AlienGrunt.CombineHere" ); PrecacheScriptSound( "NPC_AlienGrunt.GoAway" ); PrecacheScriptSound( "NPC_AlienGrunt.GetAway" ); PrecacheScriptSound( "NPC_AlienGrunt.Attack" ); PrecacheScriptSound( "NPC_AlienGrunt.Pain" ); PrecacheScriptSound( "NPC_AlienGrunt.Die" ); PrecacheScriptSound( "NPC_AlienGrunt.Shoot" ); PrecacheScriptSound( "NPC_Vortigaunt.FootstepLeft" ); PrecacheScriptSound( "NPC_Vortigaunt.FootstepRight" ); PrecacheScriptSound( "NPC_Vortigaunt.Claw" ); PrecacheScriptSound( "NPC_Vortigaunt.Swing" ); PrecacheScriptSound( "d1_trainstation_03.breakin_doorkick"); PrecacheScriptSound( "NPC_Hunter.ChargeHitEnemy" ); PrecacheScriptSound( "Water.StepLeft" ); PrecacheScriptSound( "Water.StepRight" ); BaseClass::Precache(); } int CNPC_AlienGrunt::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { // make sure friends talk about it if player hurts talkmonsters... /*int ret = BaseClass::OnTakeDamage_Alive( info ); if (!IsAlive()) { return ret; }*/ if ( !IsOnFire() && info.GetDamageType() & DMG_BURN ) { // // If we take more than ten percent of our health in burn damage within a five // second interval, we should catch on fire. // m_flBurnDamage += info.GetDamage(); m_flBurnDamageResetTime = gpGlobals->curtime + 5; if ( m_flBurnDamage >= m_iMaxHealth * 0.1 ) { Ignite( 100.0f ); } } if ( IsEFlagSet( EFL_NO_DISSOLVE ) && m_iHealth < (m_iMaxHealth / 2) ) { RemoveEFlags( EFL_NO_DISSOLVE ); } //PainSound(); /*if (cvar->FindVar("oc_ragdoll_enable_blooddrips")->GetInt()) { int MaxDripsGreen = RandomInt(1, 5); Vector vecTraceDir; if (info.GetDamageType() & (DMG_BULLET | DMG_SHOCK)) { for (int i = 0; i < MaxDripsGreen; i++) { vecTraceDir = WorldSpaceCenter(); if (LastHitGroup() == HITGROUP_CHEST) { vecTraceDir.x += random->RandomFloat(-0.1, 0.1); vecTraceDir.y += random->RandomFloat(-0.1, 0.1); vecTraceDir.z += random->RandomFloat(-0.1, 0.1); } if (LastHitGroup() == HITGROUP_HEAD) { vecTraceDir.x += random->RandomFloat(0.1, 0.4); vecTraceDir.y += random->RandomFloat(0.1, 0.4); vecTraceDir.z += random->RandomFloat(12.1, 12.4); } if (LastHitGroup() == HITGROUP_LEFTARM) { vecTraceDir.x += random->RandomFloat(0.1, 0.2); vecTraceDir.y += random->RandomFloat(-12.1, -12.4); vecTraceDir.z += random->RandomFloat(0.1, 0.2); } if (LastHitGroup() == HITGROUP_RIGHTARM) { vecTraceDir.x += random->RandomFloat(0.1, 0.2); vecTraceDir.y += random->RandomFloat(12.1, 12.4); vecTraceDir.z += random->RandomFloat(0.1, 0.2); } if (LastHitGroup() == HITGROUP_LEFTLEG) { vecTraceDir.x += random->RandomFloat(0.1, 0.2); vecTraceDir.y += random->RandomFloat(-12.1, -12.4); vecTraceDir.z += random->RandomFloat(-20.1, -20.2); } if (LastHitGroup() == HITGROUP_RIGHTLEG) { vecTraceDir.x += random->RandomFloat(0.1, 0.2); vecTraceDir.y += random->RandomFloat(12.1, 12.4); vecTraceDir.z += random->RandomFloat(-20.1, -20.2); } // Set the velocity Vector vecVelocity; AngularImpulse angImpulse; QAngle angles; angles.x = random->RandomFloat(-70, 20); angles.y = random->RandomFloat(0, 360); angles.z = 0.0f; AngleVectors(angles, &vecVelocity); vecVelocity *= random->RandomFloat(150, 300); vecVelocity += GetAbsVelocity(); angImpulse = RandomAngularImpulse(-280, 280); CBloodDripsGreen *pGrenade = (CBloodDripsGreen*)CreateEntityByName("BloodDripsGreen"); //pGrenade->SetAbsOrigin(vecTraceDir); pGrenade->SetLocalOrigin(vecTraceDir); pGrenade->SetAbsAngles(RandomAngle(0, 360)); DispatchSpawn(pGrenade); pGrenade->SetThrower(this); pGrenade->SetOwnerEntity(this); pGrenade->SetAbsVelocity(vecVelocity); } } }*/ return BaseClass::OnTakeDamage_Alive( info ); //ret; } //----------------------------------------------------------------------------- // Purpose: Returns true if a reasonable jumping distance // Input : // Output : //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos ) const { const float MAX_JUMP_RISE = 40; const float MAX_JUMP_DROP = 1024; const float MAX_JUMP_DISTANCE = 250; return BaseClass::IsJumpLegal( startPos, apex, endPos, MAX_JUMP_RISE, MAX_JUMP_DROP, MAX_JUMP_DISTANCE ); } void CNPC_AlienGrunt::PainSound ( const CTakeDamageInfo &info ) { EmitSound( "NPC_AlienGrunt.Pain" ); } void CNPC_AlienGrunt::DeathSound( const CTakeDamageInfo &info ) { EmitSound( "NPC_AlienGrunt.Die" ); } void CNPC_AlienGrunt::IdleSound( void ) { EmitSound( "NPC_AlienGrunt.Idle" ); } void CNPC_AlienGrunt::HideSound( void ) { if ( GetEnemy() != NULL && IsOkToCombatSpeak() ) { if (gpGlobals->curtime < m_HideSoundTime) return; m_HideSoundTime = gpGlobals->curtime + random->RandomFloat(2.5, 6.75); Speak( AGRUNT_HIDE ); } } //----------------------------------------------------------------------------- // Purpose: Play a random attack sound. //----------------------------------------------------------------------------- void CNPC_AlienGrunt::AttackSound( void ) { if ( GetEnemy() != NULL && IsOkToCombatSpeak() ) { Speak( AGRUNT_ATTACK ); } } void CNPC_AlienGrunt::ChargeSound( void ) { if ( GetEnemy() != NULL && IsOkToCombatSpeak() ) { Speak ( AGRUNT_CHARGE ); } } void CNPC_AlienGrunt::AlertSound( void ) { if ( GetEnemy() != NULL && IsOkToCombatSpeak() ) { Speak( AGRUNT_ALERT ); } } /*void CNPC_AlienGrunt::TraceAttack(const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator) { BaseClass::TraceAttack(inputInfo, vecDir, ptr, pAccumulator); }*/ // BJ: Armor hit react void CNPC_AlienGrunt::TraceAttack(const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator) { CTakeDamageInfo ainfo = info; float flDamage = ainfo.GetDamage(); if (ptr->hitgroup == 10 && (ainfo.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB))) { // hit armor if (m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0, 10) < 1)) { CPVSFilter filter(ptr->endpos); te->ArmorRicochet(filter, 0.0, &ptr->endpos, &ptr->plane.normal); m_flDamageTime = gpGlobals->curtime; } if (random->RandomInt(0, 1) == 0) { Vector vecTracerDir = vecDir; vecTracerDir.x += random->RandomFloat(-0.3, 0.3); vecTracerDir.y += random->RandomFloat(-0.3, 0.3); vecTracerDir.z += random->RandomFloat(-0.3, 0.3); vecTracerDir = vecTracerDir * -512; Vector vEndPos = ptr->endpos + vecTracerDir; UTIL_Tracer(ptr->endpos, vEndPos, ENTINDEX(edict())); } flDamage -= 20; if (flDamage <= 0) flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated ainfo.SetDamage(flDamage); } else { SpawnBlood(ptr->endpos, vecDir, BloodColor(), flDamage);// a little surface blood. TraceBleed(flDamage, vecDir, ptr, ainfo.GetDamageType()); } AddMultiDamage(ainfo, this); } int CNPC_AlienGrunt::TranslateSchedule( int scheduleType ) { int baseType; switch( scheduleType ) { /*case SCHED_CHASE_ENEMY: if ( HasCondition( COND_ALIENGRUNT_LOCAL_MELEE_OBSTRUCTION ) && !HasCondition(COND_TASK_FAILED) && IsCurSchedule( SCHED_CHASE_ENEMY, false ) ) { return SCHED_COMBAT_PATROL; } return SCHED_CHASE_ENEMY; break;*/ case SCHED_RUN_RANDOM: { if (GetEnemy()) { //TERO: run_random seems to make the Grunt ignore the enemy until the task is done return SCHED_ALIENGRUNT_STAND; } return BaseClass::TranslateSchedule( scheduleType ); } break; case SCHED_ALIENGRUNT_SWATITEM: // If the object is far away, move and swat it. If it's close, just swat it. if( DistToPhysicsEnt() > ALIENGRUNT_PHYSOBJ_SWATDIST ) { return SCHED_ALIENGRUNT_MOVE_SWATITEM; } else { return SCHED_ALIENGRUNT_SWATITEM; } break; case SCHED_RANGE_ATTACK1: { return SCHED_ALIENGRUNT_SHOOTBEES; break; } case SCHED_MELEE_ATTACK1: { return SCHED_ALIENGRUNT_MELEE_ATTACK; break; } case SCHED_MELEE_ATTACK2: { return SCHED_ALIENGRUNT_GRAB_RAGDOLL; break; } case SCHED_IDLE_STAND: { // call base class default so that scientist will talk // when standing during idle baseType = BaseClass::TranslateSchedule(scheduleType); if (baseType == SCHED_IDLE_STAND) { /* if (HasCondition( COND_TOO_FAR_TO_ATTACK )) return SCHED_CHASE_ENEMY; */ // just look straight ahead return SCHED_ALIENGRUNT_STAND; } else return baseType; break; } case SCHED_FAIL_ESTABLISH_LINE_OF_FIRE: { // Fail schedule doesn't go through SelectSchedule() // So we have to clear beams and glow here //ClearBeams(); //EndHandGlow(); /*if (m_hCombineMainFrame) { return SCHED_TALKER_DESTROY_COMBINE_INTERFACE; }*/ return SCHED_COMBAT_FACE; break; } case SCHED_CHASE_ENEMY_FAILED: { baseType = BaseClass::TranslateSchedule(scheduleType); if ( baseType != SCHED_CHASE_ENEMY_FAILED ) return baseType; if (HasMemory(bits_MEMORY_INCOVER)) { // Occasionally run somewhere so I don't just // stand around forever if my enemy is stationary if (random->RandomInt(0,5) == 5) { return SCHED_PATROL_RUN; } else { return SCHED_ALIENGRUNT_STAND; } } break; } case SCHED_FAIL_TAKE_COVER: { // Fail schedule doesn't go through SelectSchedule() // So we have to clear beams and glow here //ClearBeams(); //EndHandGlow(); return SCHED_RUN_RANDOM; break; } } return BaseClass::TranslateSchedule( scheduleType ); } //--------------------------------------------------------- //--------------------------------------------------------- int CNPC_AlienGrunt::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) { if ( IsPathTaskFailure( taskFailCode ) && m_hObstructor != NULL && FClassnameIs( m_hObstructor, "prop_door_rotating") && m_hObstructor->HasSpawnFlags(SF_BREAKABLE_BY_NPCS)) { m_hPhysicsEnt = m_hObstructor; m_hObstructor = NULL; //DevMsg("npc_aliengrunt: a breakable door in our way during SelectFailSchedule(), grunt smash!\n"); return SCHED_ALIENGRUNT_ATTACKITEM; } if ( IsPathTaskFailure( taskFailCode ) && m_hObstructor != NULL && m_hObstructor->VPhysicsGetObject() && m_hObstructor->VPhysicsGetObject()->GetMass() < 100 ) { if ( !FClassnameIs( m_hObstructor, "physics_prop_ragdoll" ) && !FClassnameIs( m_hObstructor, "prop_ragdoll" ) ) { m_hPhysicsEnt = m_hObstructor; m_hObstructor = NULL; return SCHED_ALIENGRUNT_ATTACKITEM; } //else // DevMsg("AlienGrunt: yikes! we almost took a ragdoll as m_hPhysicsEnt\n"); } m_hObstructor = NULL; return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); } //----------------------------------------------------------------------------- // Determines the best type of flinch anim to play. //----------------------------------------------------------------------------- Activity CNPC_AlienGrunt::GetFlinchActivity( bool bHeavyDamage, bool bGesture ) { return BaseClass::GetFlinchActivity( bHeavyDamage, true ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AlienGrunt::PlayFlinchGesture( void ) { BaseClass::PlayFlinchGesture(); // To ensure old playtested difficulty stays the same, stop cops shooting for a bit after gesture flinches if (m_flNextAttack + 0.5 < gpGlobals->curtime ) m_flNextAttack = gpGlobals->curtime + ALIENGRUNT_NEXT_ATTACK_DELAY; } int CNPC_AlienGrunt::SelectSchedule( void ) { if (HasCondition( COND_ENEMY_OCCLUDED )) { HideSound(); } /*int nSched = SelectFlinchSchedule(); if ( nSched != SCHED_NONE ) return nSched;*/ if ( BehaviorSelectSchedule() ) return BaseClass::SelectSchedule(); //TERO: moved here in case we need to open doors during alert or idle if ( HasCondition( COND_ALIENGRUNT_CAN_SWAT_ATTACK ) ) { return SCHED_ALIENGRUNT_SWATITEM; } switch( m_NPCState ) { case NPC_STATE_COMBAT: { // dead enemy if ( HasCondition( COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return BaseClass::SelectSchedule(); } if ( HasCondition( COND_ALIENGRUNT_CAN_GRAB_RAGDOLL ) ) { ClearCondition( COND_ALIENGRUNT_CANNOT_GRAB_RAGDOLL ); return SCHED_ALIENGRUNT_GRAB_RAGDOLL; } if ( HasSpawnFlags( SF_ALIENGRUNT_WAIT_FOR_GRAB ) && !HasCondition( COND_CAN_MELEE_ATTACK1 ) ) { return SCHED_CHASE_ENEMY; } if ( HasCondition( COND_ALIENGRUNT_SHOULD_CHARGE ) ) { //DevMsg("npc_aliengrunt: SCHED_ALIENGRUNT_CHARGE\n"); OccupyStrategySlot( SQUAD_SLOT_ALIENGRUNT_CHARGE ); return SCHED_ALIENGRUNT_CHARGE; } /*if (HasCondition(COND_ENEMY_OCCLUDED)) { // stand up, just in case if( GetEnemy() ) { if (!(GetEnemy()->GetFlags() & FL_NOTARGET) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 )) { // Charge in and break the enemy's cover! return SCHED_ESTABLISH_LINE_OF_FIRE; } // If I'm a long, long way away, establish a LOF anyway. Once I get there I'll // start respecting the squad slots again. float flDistSq = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() ); if ( flDistSq > Square(3000) ) return SCHED_ESTABLISH_LINE_OF_FIRE; } }*/ if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_CAN_MELEE_ATTACK1 ) && !HasCondition( COND_CAN_MELEE_ATTACK2 ) && !HasCondition(COND_NOT_FACING_ATTACK) ) //TERO: added most recently to avoid crowbar kicking { if ( HasCondition( COND_ALIENGRUNT_SHOULD_PROTECT_SQUADMATE ) ) { if ( HasCondition( COND_ALIENGRUNT_BEES ) || HasCondition( COND_CAN_RANGE_ATTACK1 ) ) { return SCHED_ALIENGRUNT_SHOOTBEES; } else if ( gpGlobals->curtime < m_flNextAttack ) { //TERO: we have just finished shooting few minutes ago, we probably will have another chance to shoot soon // let's wait and see return SCHED_ALIENGRUNT_STAND; } } //HasCondition( COND_TOO_FAR_TO_ATTACK ) && else if ( OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 )) // !HasCondition( COND_HEAR_DANGER ) && !HasCondition( COND_HEAR_MOVE_AWAY )) { return SCHED_ALIENGRUNT_PRESS_ATTACK; } } //TERO: if enemy goes behind a corner we want to shoot some bees after him //We need a better schedule here, something that shoots towards the last position we saw him } break; case NPC_STATE_ALERT: case NPC_STATE_IDLE: if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_MOVE_AWAY )) { return SCHED_TAKE_COVER_FROM_BEST_SOUND; } /*if ( HasCondition( COND_ENEMY_DEAD ) && IsOkToCombatEmitSound() ) { EmitSound( AGRUNT_KILL ); }*/ break; } return BaseClass::SelectSchedule(); } /*bool CNPC_AlienGrunt::CanBeAnEnemyOf( CBaseEntity *pEnemy ) { if ( pEnemy->Classify() == CLASS_MANHACK && (pEnemy->GetAbsOrigin() - GetAbsOrigin()).Length() > 128.0f) { return false; } return BaseClass::CanBeAnEnemyOf( CBaseEntity *pEnemy ); }*/ void CNPC_AlienGrunt::BuildScheduleTestBits( void ) { BaseClass::BuildScheduleTestBits(); /*if ( ( ConditionInterruptsCurSchedule( COND_GIVE_WAY ) || IsCurSchedule(SCHED_HIDE_AND_RELOAD ) || IsCurSchedule(SCHED_RELOAD ) || IsCurSchedule(SCHED_STANDOFF ) || IsCurSchedule(SCHED_TAKE_COVER_FROM_ENEMY ) || IsCurSchedule(SCHED_COMBAT_FACE ) || IsCurSchedule(SCHED_ALERT_FACE ) || IsCurSchedule(SCHED_COMBAT_STAND ) || IsCurSchedule(SCHED_ALERT_FACE_BESTSOUND) || IsCurSchedule(SCHED_ALERT_STAND) ) ) { SetCustomInterruptCondition( COND_PLAYER_PUSHING ); }*/ if (IsCurSchedule(SCHED_FAIL_ESTABLISH_LINE_OF_FIRE ) || IsCurSchedule(SCHED_CHASE_ENEMY)) { SetCustomInterruptCondition( COND_ALIENGRUNT_CAN_ATTACK_DOOR ); } } void CNPC_AlienGrunt::ShootBees() { CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, this, SOUNDENT_CHANNEL_WEAPON, GetEnemy() ); EmitSound( "NPC_AlienGrunt.Shoot" ); m_iBees--; if (m_iBees <= 0) { m_flNextAttack = gpGlobals->curtime + ALIENGRUNT_NEXT_ATTACK_DELAY; if (m_iHealth < 25) { m_iBees = ALIENGRUNT_BEE_BURST_SIZE_MAX; } else { m_iBees = ALIENGRUNT_BEE_BURST_SIZE_MIN; } } else { m_flNextAttack = gpGlobals->curtime + ALIENGRUNT_NEXT_ATTACK_DELAY_SMALL; } Vector vecOrigin; QAngle vecAngles; int handAttachment = LookupAttachment( "Hivehand_mouth" ); GetAttachment( handAttachment, vecOrigin, vecAngles ); // Vector forward; //GetVectors( NULL, NULL, forward ); Vector vecDir; AngleVectors(vecAngles, &vecDir); VectorNormalize( vecDir ); Vector vecVelocity; VectorMultiply( vecDir, 50, vecVelocity ); //QAngle angles; //VectorAngles( vecDir, angles ); CBee *pBee = CBee::Create( vecOrigin, vecAngles, this ); if (pBee) { pBee->m_hOwner = this; pBee->SetGracePeriod( 2 ); // pBee->CreateNavigator(); pBee->SetEnemy( GetEnemy() ); //pBee->m_hTarget = GetEnemy(); } //Lets create a "muzzle flash" UTIL_BloodDrips( vecOrigin, vecVelocity, BLOOD_COLOR_YELLOW, 1 ); /*CEffectData data; data.m_nEntIndex = entindex(); data.m_nAttachmentIndex = handAttachment; data.m_flScale = 1.0f; DispatchEffect( "ChopperMuzzleFlash", data );*/ } /*Vector CNPC_AlienGrunt::FindNodePositionForBee(Vector startPos, Vector enemyPos) { Vector vTargetDir = enemyPos - startPos; //just saving in case nodes are not found SetNavType( NAV_FLY); //we can't get there straight, lets try to find an air node int iNearestNode = GetNavigator()->GetNetwork()->NearestNodeToPoint( enemyPos, true); // GetPathfinder()->NearestNodeToPoint( enemyPos ); if ( iNearestNode != NO_NODE ) { // Get the node and move to it CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( iNearestNode ); if ( pNode ) { Vector vecNodePos = pNode->GetPosition( HULL_TINY ) + Vector(0,0,35); vTargetDir = vecNodePos - startPos; NDebugOverlay::Box( vecNodePos, Vector(10,10,10), Vector(-10,-10,-10), 255, 0, 0, 0, 5 ); DevMsg("Aliengrunt: giving node position to bee\n"); } } else DevMsg("Aliengrunt: no air node\n"); SetNavType( NAV_GROUND ); return vTargetDir; }*/ void CNPC_AlienGrunt::InputAssault( inputdata_t &inputdata ) { m_AssaultBehavior.SetParameters( AllocPooledString(inputdata.value.String()), CUE_DONT_WAIT, RALLY_POINT_SELECT_DEFAULT ); } bool CNPC_AlienGrunt::OnInsufficientStopDist( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { if ( pMoveGoal->directTrace.fStatus == AIMR_BLOCKED_ENTITY && gpGlobals->curtime >= m_flNextSwat ) { m_hObstructor = pMoveGoal->directTrace.pObstruction; } return BaseClass::OnInsufficientStopDist( pMoveGoal, distClear, pResult ); } int CNPC_AlienGrunt::GetSwatActivity( void ) { Vector vecMyCenter; Vector vecObjCenter; vecMyCenter = WorldSpaceCenter(); vecObjCenter = m_hPhysicsEnt->WorldSpaceCenter(); float flZDiff; flZDiff = vecMyCenter.z - vecObjCenter.z; if( flZDiff < 0 ) { return ACT_MELEE_ATTACK1; } return ACT_MELEE_ATTACK2; } bool CNPC_AlienGrunt::FindNearestPhysicsObject( int iMaxMass ) { CBaseEntity *pList[ ALIENGRUNT_PHYSICS_SEARCH_DEPTH ]; CBaseEntity *pNearest = NULL; float flDist; IPhysicsObject *pPhysObj; int i; Vector vecDirToEnemy; Vector vecDirToObject; //TERO: commented out because we want swats even in assaults when the Grunt may have not seen the player yet if ( !GetEnemy() ) { // Can't swat, or no enemy, so no swat. m_hPhysicsEnt = NULL; return false; } vecDirToEnemy = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); float dist = VectorNormalize(vecDirToEnemy); vecDirToEnemy.z = 0; if( dist > ALIENGRUNT_PLAYER_MAX_SWAT_DIST ) { // Player is too far away. Don't bother // trying to swat anything at them until // they are closer. return false; } float flNearestDist = min( dist, ALIENGRUNT_FARTHEST_PHYSICS_OBJECT * 0.5 ); Vector vecDelta( flNearestDist, flNearestDist, GetHullHeight() * 2.0 ); class CAlienGruntSwatEntitiesEnum : public CFlaggedEntitiesEnum { public: CAlienGruntSwatEntitiesEnum( CBaseEntity **pList, int listMax, int iMaxMass ) : CFlaggedEntitiesEnum( pList, listMax, 0 ), m_iMaxMass( iMaxMass ) { } virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) { CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); if ( pEntity && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->GetMass() <= m_iMaxMass && pEntity->VPhysicsGetObject()->GetMass() >= ALIENGRUNT_MIN_PHYSOBJ_MASS && pEntity->VPhysicsGetObject()->IsAsleep() && pEntity->VPhysicsGetObject()->IsMoveable() ) { return CFlaggedEntitiesEnum::EnumElement( pHandleEntity ); } return ITERATION_CONTINUE; } int m_iMaxMass; }; CAlienGruntSwatEntitiesEnum swatEnum( pList, ALIENGRUNT_PHYSICS_SEARCH_DEPTH, iMaxMass ); int count = UTIL_EntitiesInBox( GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, &swatEnum ); // magically know where they are Vector vecAlienGruntKnees; CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.25f ), &vecAlienGruntKnees ); for( i = 0 ; i < count ; i++ ) { pPhysObj = pList[ i ]->VPhysicsGetObject(); Assert( !( !pPhysObj || pPhysObj->GetMass() > iMaxMass || !pPhysObj->IsAsleep() ) ); Vector center = pList[ i ]->WorldSpaceCenter(); flDist = UTIL_DistApprox2D( GetAbsOrigin(), center ); if( flDist >= flNearestDist ) continue; // This object is closer... but is it between the player and the zombie? vecDirToObject = pList[ i ]->WorldSpaceCenter() - GetAbsOrigin(); VectorNormalize(vecDirToObject); vecDirToObject.z = 0; if( DotProduct( vecDirToEnemy, vecDirToObject ) < 0.8 ) continue; if( flDist >= UTIL_DistApprox2D( center, GetEnemy()->GetAbsOrigin() ) ) continue; // don't swat things where the highest point is under my knees // NOTE: This is a rough test; a more exact test is going to occur below if ( (center.z + pList[i]->BoundingRadius()) < vecAlienGruntKnees.z ) continue; // don't swat things that are over my head. if( center.z > EyePosition().z ) continue; vcollide_t *pCollide = modelinfo->GetVCollide( pList[i]->GetModelIndex() ); Vector objMins, objMaxs; physcollision->CollideGetAABB( &objMins, &objMaxs, pCollide->solids[0], pList[i]->GetAbsOrigin(), pList[i]->GetAbsAngles() ); if ( objMaxs.z < vecAlienGruntKnees.z ) continue; if ( !FVisible( pList[i] ) ) continue; if ( hl2_episodic.GetBool() ) { // Skip things that the enemy can't see. Do we want this as a general thing? // The case for this feature is that zombies who are pursuing the player will // stop along the way to swat objects at the player who is around the corner or // otherwise not in a place that the object has a hope of hitting. This diversion // makes the zombies very late (in a random fashion) getting where they are going. (sjb 1/2/06) if( !GetEnemy()->FVisible( pList[i] ) ) continue; } // Make this the last check, since it makes a string. // Don't swat server ragdolls! -TERO: why the hell not? -because it makes the game crash, dumbass! -oh... sorry... if ( FClassnameIs( pList[ i ], "physics_prop_ragdoll" ) ) continue; if ( FClassnameIs( pList[ i ], "prop_ragdoll" ) ) continue; // The object must also be closer to the zombie than it is to the enemy pNearest = pList[ i ]; flNearestDist = flDist; } m_hPhysicsEnt = pNearest; if( m_hPhysicsEnt == NULL ) { return false; } else { return true; } } float CNPC_AlienGrunt::DistToPhysicsEnt( void ) { //return ( GetLocalOrigin() - m_hPhysicsEnt->GetLocalOrigin() ).Length(); if ( m_hPhysicsEnt != NULL ) return UTIL_DistApprox2D( GetAbsOrigin(), m_hPhysicsEnt->WorldSpaceCenter() ); return ALIENGRUNT_PHYSOBJ_SWATDIST + 1; } /*void CNPC_AlienGrunt::GatherEnemyConditions( CBaseEntity *pEnemy ) { DevMsg("Gathering enemy conditions\n"); if (FCanCheckAttacks()) { DevMsg("FCanCheckAttacks()\n"); } BaseClass::GatherEnemyConditions( pEnemy ); }*/ //--------------------------------------------------------- //--------------------------------------------------------- void CNPC_AlienGrunt::GatherConditions( void ) { ClearCondition( COND_ALIENGRUNT_LOCAL_MELEE_OBSTRUCTION ); //DevMsg("Gathering conditions\n"); BaseClass::GatherConditions(); /*if (HasCondition( COND_CAN_RANGE_ATTACK1 )) { DevMsg("yay\n"); }*/ if( m_NPCState == NPC_STATE_COMBAT && m_hSetTarget == NULL && m_hDoor == NULL) { // This check for !m_pPhysicsEnt prevents a crashing bug, but also // eliminates the zombie picking a better physics object if one happens to fall // between him and the object he's heading for already. if( gpGlobals->curtime >= m_flNextSwatScan && (m_hPhysicsEnt == NULL) ) { FindNearestPhysicsObject( ALIENGRUNT_MAX_PHYSOBJ_MASS ); m_flNextSwatScan = gpGlobals->curtime + 2.0; } } if ( m_hDoor != NULL ) { m_hPhysicsEnt = m_hDoor; SetCondition( COND_ALIENGRUNT_CAN_ATTACK_DOOR ); SetCondition( COND_ALIENGRUNT_CAN_SWAT_ATTACK ); } else if ( m_hSetTarget != NULL ) { m_hPhysicsEnt = m_hSetTarget; SetCondition( COND_ALIENGRUNT_CAN_SWAT_ATTACK ); } else if(((m_hPhysicsEnt != NULL) && gpGlobals->curtime >= m_flNextSwat) && ( HasCondition( COND_SEE_ENEMY ) || FClassnameIs( m_hPhysicsEnt, "prop_door_rotating") )) { SetCondition( COND_ALIENGRUNT_CAN_SWAT_ATTACK ); } else { ClearCondition( COND_ALIENGRUNT_CAN_SWAT_ATTACK ); } bool bFound = false; if ( m_pSquad ) { AISquadIter_t iter; CAI_BaseNPC *pSquadmate = m_pSquad->GetFirstMember( &iter ); while ( pSquadmate && !bFound) { if ( pSquadmate != this ) { if ( pSquadmate->Classify() == CLASS_ALIENGRUNT && pSquadmate->IsCurSchedule( SCHED_ALIENGRUNT_CHARGE ) ) { bFound = true; } if ( pSquadmate->Classify() == CLASS_VORTIGAUNT && pSquadmate->HasCondition( COND_CAN_RANGE_ATTACK1 ) ) { bFound = true; } /*if ( pSquadmate->Classify() == CLASS_ALIENCONTROLLER && pSquadmate->HasCondition( COND_ALIENCONTROLLER_CAN_PHYS_ATTACK )) { bFound = true; }*/ if ( pSquadmate->HasCondition( COND_CAN_MELEE_ATTACK1 ) || pSquadmate->HasCondition(COND_CAN_MELEE_ATTACK2 ) ) { bFound = true; } } pSquadmate = m_pSquad->GetNextMember( &iter ); } } if (bFound) { SetCondition( COND_ALIENGRUNT_SHOULD_PROTECT_SQUADMATE ); } else { ClearCondition( COND_ALIENGRUNT_SHOULD_PROTECT_SQUADMATE ); } // See if we can charge the target if ( GetEnemy() ) { if ( ShouldCharge( GetAbsOrigin(), GetEnemy()->GetAbsOrigin(), true, false ) ) { SetCondition( COND_ALIENGRUNT_SHOULD_CHARGE ); } else { ClearCondition( COND_ALIENGRUNT_SHOULD_CHARGE ); } } } //----------------------------------------------------------------------------- // Purpose: Calculate & apply damage & force for a charge to a target. // Done outside of the guard because we need to do this inside a trace filter. //----------------------------------------------------------------------------- void ApplyChargeDamageAlienGrunt( CBaseEntity *pAlienGrunt, CBaseEntity *pTarget, float flDamage ) { Vector attackDir = ( pTarget->WorldSpaceCenter() - pAlienGrunt->WorldSpaceCenter() ); VectorNormalize( attackDir ); Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter(); // Generate enough force to make a 75kg guy move away at 700 in/sec Vector vecForce = attackDir * ImpulseScale( 75, 700 ); // Deal the damage CTakeDamageInfo info( pAlienGrunt, pAlienGrunt, vecForce, offset, flDamage, DMG_CLUB ); pTarget->TakeDamage( info ); } //----------------------------------------------------------------------------- // Purpose: While charging, look ahead and see if we're going to run into anything. // If we are, start the gesture so it looks like we're anticipating the hit. //----------------------------------------------------------------------------- void CNPC_AlienGrunt::ChargeLookAhead( void ) { trace_t tr; Vector vecForward; GetVectors( &vecForward, NULL, NULL ); Vector vecTestPos = GetAbsOrigin() + ( vecForward * m_flGroundSpeed * 0.75 ); Vector testHullMins = GetHullMins(); testHullMins.z += (StepHeight() * 2); TraceHull_SkipPhysics( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5 ); //NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f ); //NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f ); /*if ( tr.fraction != 1.0 ) { // Start playing the hit animation AddGesture( ACT_ANTLIONGUARD_CHARGE_ANTICIPATION ); }*/ } //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // radius - // magnitude - //----------------------------------------------------------------------------- void CNPC_AlienGrunt::ImpactShock( const Vector &origin, float radius, float magnitude, CBaseEntity *pIgnored ) { // Also do a local physics explosion to push objects away float adjustedDamage, flDist; Vector vecSpot; float falloff = 1.0f / 2.5f; CBaseEntity *pEntity = NULL; // Find anything within our radius while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, origin, radius ) ) != NULL ) { // Don't affect the ignored target if ( pEntity == pIgnored ) continue; if ( pEntity == this ) continue; CBasePropDoor *pDoor = dynamic_cast(pEntity); if (pDoor) { if (pDoor->HasSpawnFlags(SF_BREAKABLE_BY_NPCS) && (WorldSpaceCenter() - pDoor->WorldSpaceCenter()).Length() < radius ) { // add some spin so the object doesn't appear to just fly in a straight line // Also this spin will move the object slightly as it will press on whatever the object // is resting on. AngularImpulse angVelocity( random->RandomFloat(-180, 180), 20, random->RandomFloat(-360, 360) ); pDoor->BreakDoors(WorldSpaceCenter(), angVelocity); EmitSound("d1_trainstation_03.breakin_doorkick"); } } // UNDONE: Ask the object if it should get force if it's not MOVETYPE_VPHYSICS? else if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || ( pEntity->VPhysicsGetObject() && pEntity->IsPlayer() == false ) ) { vecSpot = pEntity->BodyTarget( WorldSpaceCenter() ); // decrease damage for an ent that's farther from the bomb. flDist = ( WorldSpaceCenter() - vecSpot ).Length(); if ( flDist <= radius ) //radius == 0 || flDist <= radius ) { /*adjustedDamage = flDist * falloff; adjustedDamage = magnitude - adjustedDamage; if ( adjustedDamage < 1 ) { adjustedDamage = 1; }*/ //DevMsg("dist %f, rad %f, ", flDist, radius ); adjustedDamage = radius - flDist; //DevMsg("rad - dist %f, ", adjustedDamage); adjustedDamage *= falloff; //DevMsg("times fallof %f, ", adjustedDamage ); adjustedDamage = clamp(adjustedDamage, 1, magnitude ); //DevMsg("%s, damage/magnitude %f/%f, dist/radius %f/%f\n", pEntity->GetDebugName(), adjustedDamage, magnitude, flDist, radius ); CTakeDamageInfo info( this, this, adjustedDamage, DMG_BLAST ); Vector vecDir = (vecSpot - GetAbsOrigin()); CalculateExplosiveDamageForce( &info, vecDir, GetAbsOrigin() ); pEntity->VPhysicsTakeDamage( info ); } } } //TERO: lets add some more time m_flChargeCancelTime = m_flChargeCancelTime + 1.0f; } //----------------------------------------------------------------------------- // Purpose: Return true if our charge target is right in front of the guard // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity ) { if ( !GetEnemy() ) return false; if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) ) { Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); vecLOS.z = 0; VectorNormalize( vecLOS ); Vector vBodyDir = BodyDirection2D(); if ( DotProduct( vecLOS, vBodyDir ) > 0.8 ) { // He's in front of me, and close. Make sure he's not behind a wall. trace_t tr; UTIL_TraceLine( WorldSpaceCenter(), GetEnemy()->EyePosition(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.m_pEnt == GetEnemy() ) { *pEntity = tr.m_pEnt; return true; } } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : *pTarget - //----------------------------------------------------------------------------- void CNPC_AlienGrunt::ChargeDamage( CBaseEntity *pTarget ) { if ( pTarget == NULL ) return; CBasePlayer *pPlayer = ToBasePlayer( pTarget ); if ( pPlayer != NULL ) { //Kick the player angles pPlayer->ViewPunch( QAngle( 20, 20, -30 ) ); Vector dir = pPlayer->WorldSpaceCenter() - WorldSpaceCenter(); VectorNormalize( dir ); dir.z = 0.0f; Vector vecNewVelocity = dir * 180.0f; vecNewVelocity[2] += 128.0f; pPlayer->SetAbsVelocity( vecNewVelocity ); color32 red = {128,0,0,128}; UTIL_ScreenFade( pPlayer, red, 1.0f, 0.1f, FFADE_IN ); } // Player takes less damage float flDamage = (pTarget->Classify() == CLASS_PLAYER_ALLY_VITAL) ? (sk_aliengrunt_dmg_claw.GetFloat() * 0.75f) : (sk_aliengrunt_dmg_claw.GetFloat()); // If it's being held by the player, break that bond Pickup_ForcePlayerToDropThisObject( pTarget ); // Calculate the physics force ApplyChargeDamageAlienGrunt( this, pTarget, flDamage ); } //----------------------------------------------------------------------------- // Purpose: Handles the guard charging into something. Returns true if it hit the world. //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity ) { // Cause a shock wave from this point which will disrupt nearby physics objects ImpactShock( vecImpact, 100, 30 ); //AntlionGuard has magnitude of 350 // Did we hit anything interesting? if ( !pEntity || pEntity->IsWorld() ) { // Robin: Due to some of the finicky details in the motor, the guard will hit // the world when it is blocked by our enemy when trying to step up // during a moveprobe. To get around this, we see if the enemy's within // a volume in front of the guard when we hit the world, and if he is, // we hit him anyway. EnemyIsRightInFrontOfMe( &pEntity ); // Did we manage to find him? If not, increment our charge miss count and abort. if ( pEntity->IsWorld() ) { //m_iChargeMisses++; return true; } } // Hit anything we don't like if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) ) { EmitSound( "NPC_Hunter.ChargeHitEnemy" ); EmitSound( "NPC_AlienGrunt.Pain", 0.5f ); if ( GetActivity() != ACT_ALIENGRUNT_CHARGE_HIT ) //!IsPlayingGesture( ACT_ALIENGRUNT ) { SetActivity( ACT_ALIENGRUNT_CHARGE_HIT ); } ChargeDamage( pEntity ); pEntity->ApplyAbsVelocityImpulse( ( BodyDirection2D() * 400 ) + Vector( 0, 0, 10 ) ); if ( !pEntity->IsAlive() && GetEnemy() == pEntity ) { SetEnemy( NULL ); } SetNextAttack( gpGlobals->curtime + 2.0f ); //SetActivity( ACT_ALIENGRUNT_CHARGE_CANCEL ); // We've hit something, so clear our miss count // m_iChargeMisses = 0; return false; } // Hit something we don't hate. If it's not moveable, crash into it. if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH ) return true; // If it's a vphysics object that's too heavy, crash into it too. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); if ( pPhysics ) { // If the object is being held by the player, knock it out of his hands if ( pPhysics->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) { Pickup_ForcePlayerToDropThisObject( pEntity ); return false; } if ( (!pPhysics->IsMoveable() || pPhysics->GetMass() > VPhysicsGetObject()->GetMass() * 0.5f ) ) return true; } } return false; } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float CNPC_AlienGrunt::ChargeSteer( void ) { trace_t tr; Vector testPos, steer, forward, right; QAngle angles; const float testLength = m_flGroundSpeed * 0.15f; //Get our facing GetVectors( &forward, &right, NULL ); steer = forward; const float faceYaw = UTIL_VecToYaw( forward ); //Offset right VectorAngles( forward, angles ); angles[YAW] += 45.0f; AngleVectors( angles, &forward ); //Probe out testPos = GetAbsOrigin() + ( forward * testLength ); //Offset by step height Vector testHullMins = GetHullMins(); testHullMins.z += (StepHeight() * 2); //Probe TraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f ); //Debug info /*if ( g_debug_antlionguard.GetInt() == 1 ) { if ( tr.fraction == 1.0f ) { NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f ); } else { NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f ); } }*/ //Add in this component steer += ( right * 0.5f ) * ( 1.0f - tr.fraction ); //Offset left angles[YAW] -= 90.0f; AngleVectors( angles, &forward ); //Probe out testPos = GetAbsOrigin() + ( forward * testLength ); // Probe TraceHull_SkipPhysics( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr, VPhysicsGetObject()->GetMass() * 0.5f ); //Debug /* if ( g_debug_antlionguard.GetInt() == 1 ) { if ( tr.fraction == 1.0f ) { NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f ); } else { NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f ); } }*/ //Add in this component steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction ); //Debug /*if ( g_debug_antlionguard.GetInt() == 1 ) { NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f ); NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f ); NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f ); NDebugOverlay::Cross3D( GetAbsOrigin() + ( BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f ); }*/ return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw ); } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::ShouldCharge( const Vector &startPos, const Vector &endPos, bool useTime, bool bCheckForCancel ) { // Must have a target if ( !GetEnemy() ) return false; // No one else in the squad can be charging already if ( IsStrategySlotRangeOccupied( SQUAD_SLOT_ALIENGRUNT_CHARGE, SQUAD_SLOT_ALIENGRUNT_CHARGE ) ) return false; // Don't check the distance once we start charging if ( !bCheckForCancel ) { if (useTime && !IsOnFire()) { float flTime = RemapValClamped( (float)m_iHealth, 20, m_iMaxHealth, aliengrunt_charge_delay_min.GetFloat(), aliengrunt_charge_delay_max.GetFloat()); //DevMsg("Charge time, health left %d/%d, next charge in %f\n", m_iHealth, m_iMaxHealth, flTime); // Don't allow use to charge again if it's been too soon if ( (m_flNextChargeTime + flTime) > gpGlobals->curtime ) return false; } float distance = UTIL_DistApprox2D( startPos, endPos ); // Must be within our tolerance range if ( ( distance < ALIENGRUNT_CHARGE_MIN ) || ( distance > ALIENGRUNT_CHARGE_MAX ) ) return false; } if ( GetSquad() ) { // If someone in our squad is closer to the enemy, then don't charge (we end up hitting them more often than not!) float flOurDistToEnemySqr = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr(); AISquadIter_t iter; for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) ) { if ( pSquadMember->IsAlive() == false || pSquadMember == this ) continue; if ( ( pSquadMember->GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).LengthSqr() < flOurDistToEnemySqr ) return false; } } //FIXME: We'd like to exclude small physics objects from this check! // We only need to hit the endpos with the edge of our bounding box Vector vecDir = endPos - startPos; VectorNormalize( vecDir ); float flWidth = WorldAlignSize().x * 0.5; Vector vecTargetPos = endPos - (vecDir * flWidth); // See if we can directly move there AIMoveTrace_t moveTrace; GetMoveProbe()->MoveLimit( NAV_GROUND, startPos, vecTargetPos, MASK_NPCSOLID_BRUSHONLY, GetEnemy(), &moveTrace ); // Draw the probe /*if ( g_debug_antlionguard.GetInt() == 1 ) { Vector enemyDir = (vecTargetPos - startPos); float enemyDist = VectorNormalize( enemyDir ); NDebugOverlay::BoxDirection( startPos, GetHullMins(), GetHullMaxs() + Vector(enemyDist,0,0), enemyDir, 0, 255, 0, 8, 1.0f ); }*/ // If we're not blocked, charge if ( IsMoveBlocked( moveTrace ) ) { // Don't allow it if it's too close to us if ( UTIL_DistApprox( WorldSpaceCenter(), moveTrace.vEndPosition ) < ALIENGRUNT_CHARGE_MIN ) return false; // Allow some special cases to not block us if ( moveTrace.pObstruction != NULL ) { // If we've hit the world, see if it's a cliff if ( moveTrace.pObstruction == GetContainingEntity( INDEXENT(0) ) ) { // Can't be too far above/below the target if ( fabs( moveTrace.vEndPosition.z - vecTargetPos.z ) > StepHeight() ) return false; // Allow it if we got pretty close if ( UTIL_DistApprox( moveTrace.vEndPosition, vecTargetPos ) < 64 ) return true; } // Hit things that will take damage if ( moveTrace.pObstruction->m_takedamage != DAMAGE_NO ) return true; // Hit things that will move if ( moveTrace.pObstruction->GetMoveType() == MOVETYPE_VPHYSICS ) return true; } return false; } // Only update this if we've requested it if ( useTime ) { m_flNextChargeTime = gpGlobals->curtime; // + flTime; } return true; } //----------------------------------------------------------------------------- // Purpose: // Input : flInterval - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::OverrideMove( float flInterval ) { // If the guard's charging, we're handling the movement if ( IsCurSchedule( SCHED_ALIENGRUNT_CHARGE ) ) return true; return BaseClass::OverrideMove( flInterval ); } //----------------------------------------------------------------------------- // Purpose: turn in the direction of movement // Output : //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval ) { //TERO: we have to check how far away the enemy is, and if we have object to smash if ( m_hPhysicsEnt && (m_hPhysicsEnt->WorldSpaceCenter() - WorldSpaceCenter()).Length() < ALIENGRUNT_FACE_PHYSICS_ENT_DISTANCE && (IsCurSchedule( SCHED_ALIENGRUNT_MOVE_SWATITEM, false ) || IsCurSchedule( SCHED_ALIENGRUNT_SWATITEM, false ) || IsCurSchedule( SCHED_ALIENGRUNT_ATTACKITEM, false ) )) { AddFacingTarget( m_hPhysicsEnt, m_hPhysicsEnt->WorldSpaceCenter(), 3.0f, 0.2f ); return BaseClass::OverrideMoveFacing( move, flInterval ); } if ( GetEnemy() && (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).Length() < ALIENGRUNT_FACE_ENEMY_DISTANCE ) { AddFacingTarget( GetEnemy(), GetEnemy()->WorldSpaceCenter(), 1.0f, 0.2f ); return BaseClass::OverrideMoveFacing( move, flInterval ); } return BaseClass::OverrideMoveFacing( move, flInterval ); } //----------------------------------------------------------------------------- // Continuous movement tasks //----------------------------------------------------------------------------- bool CNPC_AlienGrunt::IsCurTaskContinuousMove() { const Task_t* pTask = GetTask(); if ( pTask && (pTask->iTask == TASK_ALIENGRUNT_CHASE_ENEMY_CONTINUOUSLY) ) return true; return BaseClass::IsCurTaskContinuousMove(); } //----------------------------------------------------------------------------- // Chase the enemy, updating the target position as the player moves //----------------------------------------------------------------------------- void CNPC_AlienGrunt::StartTaskChaseEnemyContinuously( const Task_t *pTask ) { //DevMsg("StartTaskChaseEnemyContinuously( pTask );\n"); CBaseEntity *pEnemy = GetEnemy(); if ( !pEnemy ) { TaskFail( FAIL_NO_ENEMY ); return; } // We're done once we get close enough /*if ( WorldSpaceCenter().DistToSqr( pEnemy->WorldSpaceCenter() ) <= pTask->flTaskData * pTask->flTaskData ) { TaskComplete(); return; }*/ // TASK_GET_PATH_TO_ENEMY if ( IsUnreachable( pEnemy ) ) { TaskFail(FAIL_NO_ROUTE); return; } if ( !GetNavigator()->SetGoal( GOALTYPE_ENEMY, AIN_NO_PATH_TASK_FAIL ) ) { // no way to get there =( DevWarning( 2, "GetPathToEnemy failed!!\n" ); RememberUnreachable( pEnemy ); TaskFail(FAIL_NO_ROUTE); return; } // NOTE: This is TaskRunPath here. if ( TranslateActivity( ACT_RUN ) != ACT_INVALID ) { GetNavigator()->SetMovementActivity( ACT_RUN ); } else { GetNavigator()->SetMovementActivity(ACT_WALK); } // Cover is void once I move Forget( bits_MEMORY_INCOVER ); if (GetNavigator()->GetGoalType() == GOALTYPE_NONE) { TaskComplete(); GetNavigator()->ClearGoal(); // Clear residual state return; } if (!GetNavigator()->IsGoalActive()) { SetIdealActivity( GetStoppedActivity() ); } else { // Check validity of goal type ValidateNavGoal(); } // set that we're probably going to stop before the goal GetNavigator()->SetArrivalDistance( 64.0f ); //pTask->flTaskData ); m_vSavePosition = GetEnemy()->WorldSpaceCenter(); } void CNPC_AlienGrunt::RunTaskChaseEnemyContinuously( const Task_t *pTask ) { //DevMsg("RunTaskChaseEnemyContinuously( pTask );\n"); if (!GetNavigator()->IsGoalActive()) { SetIdealActivity( GetStoppedActivity() ); } else { // Check validity of goal type ValidateNavGoal(); } CBaseEntity *pEnemy = GetEnemy(); if ( !pEnemy ) { TaskFail( FAIL_NO_ENEMY ); return; } // We're done once we get close enough /*if ( WorldSpaceCenter().DistToSqr( pEnemy->WorldSpaceCenter() ) <= pTask->flTaskData * pTask->flTaskData ) { GetNavigator()->StopMoving(); TaskComplete(); return; }*/ // Recompute path if the enemy has moved too much if ( m_vSavePosition.DistToSqr( pEnemy->WorldSpaceCenter() ) < (pTask->flTaskData * pTask->flTaskData) ) return; if ( IsUnreachable( pEnemy ) ) { TaskFail(FAIL_NO_ROUTE); return; } if ( !GetNavigator()->RefindPathToGoal() ) { TaskFail(FAIL_NO_ROUTE); return; } m_vSavePosition = pEnemy->WorldSpaceCenter(); } //----------------------------------------------------------------------------- // Purpose: A simple trace filter class to skip small moveable physics objects //----------------------------------------------------------------------------- class CTraceFilterSkipPhysics : public CTraceFilter { public: // It does have a base, but we'll never network anything below here.. DECLARE_CLASS_NOBASE( CTraceFilterSkipPhysics ); CTraceFilterSkipPhysics( const IHandleEntity *passentity, int collisionGroup, float minMass ) : m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass) { } virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { if ( !StandardFilterRules( pHandleEntity, contentsMask ) ) return false; if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) return false; // Don't test if the game code tells us we should ignore this collision... CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); if ( pEntity ) { if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) ) return false; if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) ) return false; // don't test small moveable physics objects (unless it's an NPC) if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); Assert(pPhysics); if ( pPhysics->IsMoveable() && pPhysics->GetMass() < m_minMass ) return false; } // If we hit an antlion, don't stop, but kill it if ( pEntity->Classify() == CLASS_CITIZEN_REBEL ) { CBaseEntity *pGuard = (CBaseEntity*)EntityFromEntityHandle( m_pPassEnt ); ApplyChargeDamageAlienGrunt( pGuard, pEntity, pEntity->GetHealth() ); return false; } } return true; } private: const IHandleEntity *m_pPassEnt; int m_collisionGroup; float m_minMass; }; inline void TraceHull_SkipPhysics( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin, const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore, int collisionGroup, trace_t *ptr, float minMass ) { Ray_t ray; ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax ); CTraceFilterSkipPhysics traceFilter( ignore, collisionGroup, minMass ); enginetrace->TraceRay( ray, mask, &traceFilter, ptr ); } //------------------------------------------------------------------------------ // // Schedules // //------------------------------------------------------------------------------ AI_BEGIN_CUSTOM_NPC( npc_aliengrunt, CNPC_AlienGrunt ) DECLARE_USES_SCHEDULE_PROVIDER( CAI_AssaultBehavior ) DECLARE_TASK( TASK_ALIENGRUNT_SWAT_ITEM ) DECLARE_TASK( TASK_ALIENGRUNT_GET_PATH_TO_PHYSOBJ ) DECLARE_TASK( TASK_ALIENGRUNT_SHOOT_BEES ) DECLARE_TASK( TASK_ALIENGRUNT_CHARGE ) DECLARE_TASK( TASK_ALIENGRUNT_CHASE_ENEMY_CONTINUOUSLY ) DECLARE_SQUADSLOT( SQUAD_SLOT_ALIENGRUNT_CHARGE ) DECLARE_ACTIVITY( ACT_ALIENGRUNT_TO_ACTION ) DECLARE_ACTIVITY( ACT_ALIENGRUNT_TO_IDLE) DECLARE_ACTIVITY( ACT_ALIENGRUNT_CHARGE_LOOP ) DECLARE_ACTIVITY( ACT_ALIENGRUNT_CHARGE_HIT ) DECLARE_ACTIVITY( ACT_ALIENGRUNT_CHARGE_CANCEL ) DECLARE_ACTIVITY( ACT_ALIENGRUNT_CHARGE_START ) DECLARE_ACTIVITY( ACT_ALIENGRUNT_GRAB_RAGDOLL ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_GRAB_RAGDOLL ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_THROW_RAGDOLL ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_HAND_LEFT ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_HAND_RIGHT ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_SHOOTBEES ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_SWING_SOUND ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_CHARGE_HIT ) #ifdef ALIENGRUNT_CUSTOM_FOOT_AE DECLARE_ANIMEVENT( AE_ALIENGRUNT_FOOT_LEFT ) DECLARE_ANIMEVENT( AE_ALIENGRUNT_FOOT_RIGHT ) #endif DECLARE_CONDITION( COND_ALIENGRUNT_CAN_SWAT_ATTACK ) DECLARE_CONDITION( COND_ALIENGRUNT_LOCAL_MELEE_OBSTRUCTION ) DECLARE_CONDITION( COND_ALIENGRUNT_CAN_GRAB_RAGDOLL ) DECLARE_CONDITION( COND_ALIENGRUNT_CANNOT_GRAB_RAGDOLL ) DECLARE_CONDITION( COND_ALIENGRUNT_SHOULD_CHARGE ) DECLARE_CONDITION( COND_ALIENGRUNT_CAN_ATTACK_DOOR ) DECLARE_CONDITION( COND_ALIENGRUNT_BEES ) DECLARE_CONDITION( COND_ALIENGRUNT_SHOULD_PROTECT_SQUADMATE ) //========================================================= // > SCHED_ALIENGRUNT_RANGE_ATTACK //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_SHOOTBEES, " Tasks" " TASK_STOP_MOVING 0" // commented out so that we can choose our anim " TASK_FACE_IDEAL 0" //TASK_ADD_GESTURE_WAIT " TASK_RANGE_ATTACK1 0" //TASK_RANGE_ATTACK1 TASK_ALIENGRUNT_SHOOT_BEES " TASK_RANGE_ATTACK1 0" " TASK_RANGE_ATTACK1 0" // " TASK_SET_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" //" TASK_WAIT 0.1" // Wait a sec before firing again "" " Interrupts" " COND_CAN_MELEE_ATTACK1" " COND_ALIENGRUNT_CAN_GRAB_RAGDOLL" " COND_ALIENGRUNT_SHOULD_CHARGE" " COND_HEAVY_DAMAGE" ); //========================================================= // > SCHED_ALIENGRUNT_MELEE_ATTACK //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_MELEE_ATTACK, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_FACE_ENEMY 0" " TASK_MELEE_ATTACK1 ACTIVITY:ACT_MELEE_ATTACK1" " TASK_WAIT 0.3" "" " Interrupts" " COND_ENEMY_DEAD" " COND_ENEMY_OCCLUDED" " COND_ALIENGRUNT_CAN_GRAB_RAGDOLL" ); //========================================================= // > SCHED_ALIENGRUNT_STAND //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_STAND, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_WAIT 2" // repick IDLESTAND every two seconds." "" " Interrupts" " COND_NEW_ENEMY" " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" " COND_SMELL" " COND_PROVOKED" " COND_HEAR_COMBAT" " COND_HEAR_DANGER" " COND_ALIENGRUNT_SHOULD_CHARGE" " COND_ALIENGRUNT_CAN_SWAT_ATTACK" " COND_ALIENGRUNT_CAN_GRAB_RAGDOLL" " COND_ALIENGRUNT_CAN_ATTACK_DOOR" ); //========================================================= // > SCHED_ALIENGRUNT_MOVE_SWATITEM //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_MOVE_SWATITEM, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" " TASK_ALIENGRUNT_GET_PATH_TO_PHYSOBJ 0" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_FACE_ENEMY 0" " TASK_ALIENGRUNT_SWAT_ITEM 0" "" " Interrupts" " COND_ENEMY_DEAD" " COND_NEW_ENEMY" ) //========================================================= // SwatItem //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_SWATITEM, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" " TASK_FACE_ENEMY 0" " TASK_ALIENGRUNT_SWAT_ITEM 0" "" " Interrupts" " COND_ENEMY_DEAD" " COND_NEW_ENEMY" ); //========================================================= //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_ATTACKITEM, " Tasks" " TASK_FACE_ENEMY 0" " TASK_MELEE_ATTACK1 0" "" " Interrupts" " COND_ENEMY_DEAD" " COND_NEW_ENEMY" ); //========================================================= // > SCHED_ALIENGRUNT_GRAB_RAGDOLL //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_GRAB_RAGDOLL, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_FACE_ENEMY 0" " TASK_MELEE_ATTACK2 ACTIVITY:ACT_ALIENGRUNT_GRAB_RAGDOLL" "" " Interrupts" " COND_ALIENGRUNT_CANNOT_GRAB_RAGDOLL" ); //========================================================= // SCHED_COMBINE_PRESS_ATTACK //========================================================= DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_PRESS_ATTACK, " Tasks " " TASK_STOP_MOVING 0" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" " TASK_ALIENGRUNT_CHASE_ENEMY_CONTINUOUSLY 192" " TASK_FACE_ENEMY 0" "" " Interrupts " " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_ENEMY_UNREACHABLE" " COND_TOO_CLOSE_TO_ATTACK" " COND_CAN_MELEE_ATTACK1" " COND_CAN_RANGE_ATTACK1" " COND_CAN_MELEE_ATTACK2" " COND_ALIENGRUNT_CAN_GRAB_RAGDOLL" " COND_ALIENGRUNT_SHOULD_CHARGE" " COND_ALIENGRUNT_CAN_ATTACK_DOOR" " COND_ALIENGRUNT_SHOULD_PROTECT_SQUADMATE" ); //================================================== // SCHED_ALIENGRUNT_CHARGE //================================================== DEFINE_SCHEDULE ( SCHED_ALIENGRUNT_CHARGE, " Tasks" " TASK_STOP_MOVING 0" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ALIENGRUNT_PRESS_ATTACK" " TASK_FACE_ENEMY 0" " TASK_ALIENGRUNT_CHARGE 0" "" " Interrupts" " COND_TASK_FAILED" " COND_HEAVY_DAMAGE" ); AI_END_CUSTOM_NPC()