//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: Antlion Grub - cannon fodder // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "AI_Task.h" #include "AI_Default.h" #include "AI_Schedule.h" #include "AI_Hull.h" #include "AI_Navigator.h" #include "activitylist.h" #include "game.h" #include "NPCEvent.h" #include "Player.h" #include "EntityList.h" #include "AI_Interactions.h" #include "soundent.h" #include "Gib.h" #include "soundenvelope.h" #include "shake.h" #include "Sprite.h" #include "vstdlib/random.h" #include "npc_antliongrub.h" #include "engine/IEngineSound.h" #include "te_effect_dispatch.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar sk_antliongrub_health( "sk_antliongrub_health", "0" ); #define ANTLIONGRUB_SQUEAL_DIST 512 #define ANTLIONGRUB_SQUASH_TIME 1.0f #define ANTLIONGRUB_HEAL_RANGE 256.0f #define ANTLIONGRUB_HEAL_CONE 0.5f #define ANTLIONGRUB_ENEMY_HOSTILE_TIME 8.0f #include "AI_BaseNPC.h" #include "soundenvelope.h" #include "bitstring.h" class CSprite; #define ANTLIONGRUB_MODEL "models/antlion_grub.mdl" #define ANTLIONGRUB_SQUASHED_MODEL "models/antlion_grub_squashed.mdl" class CNPC_AntlionGrub : public CAI_BaseNPC { public: DECLARE_CLASS( CNPC_AntlionGrub, CAI_BaseNPC ); DECLARE_DATADESC(); CNPC_AntlionGrub( void ); virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info ); virtual int SelectSchedule( void ); virtual int TranslateSchedule( int type ); int MeleeAttack1Conditions( float flDot, float flDist ); bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sourceEnt ); void Precache( void ); void Spawn( void ); void StartTask( const Task_t *pTask ); void RunTask( const Task_t *pTask ); void GrubTouch( CBaseEntity *pOther ); void EndTouch( CBaseEntity *pOther ); void PainSound( const CTakeDamageInfo &info ); void PrescheduleThink( void ); void HandleAnimEvent( animevent_t *pEvent ); void Event_Killed( const CTakeDamageInfo &info ); void BuildScheduleTestBits( void ); void StopLoopingSounds(); float MaxYawSpeed( void ) { return 2.0f; } float StepHeight( ) const { return 12.0f; } //NOTENOTE: We don't want them to move up too high Class_T Classify( void ) { return CLASS_ANTLION; } private: void AttachToSurface( void ); void SpawnSquashedGrub( void ); void Squash( CBaseEntity *pOther ); void BroadcastAlert( void ); /* CSoundPatch *m_pMovementSound; CSoundPatch *m_pVoiceSound; CSoundPatch *m_pHealSound; */ CSprite *m_pGlowSprite; float m_flNextVoiceChange; //The next point to alter our voice float m_flSquashTime; //Amount of time we've been stepped on float m_flNearTime; //Amount of time the player has been near enough to be considered for healing float m_flHealthTime; //Amount of time until the next piece of health is given float m_flEnemyHostileTime; //Amount of time the enemy should be avoided (because they displayed aggressive behavior) bool m_bMoving; bool m_bSquashed; bool m_bSquashValid; bool m_bHealing; int m_nHealthReserve; int m_nGlowSpriteHandle; DEFINE_CUSTOM_AI; }; //Sharp fast attack envelopePoint_t envFastAttack[] = { { 0.3f, 0.5f, //Attack 0.5f, 1.0f, }, { 0.0f, 0.1f, //Decay 0.1f, 0.2f, }, { -1.0f, -1.0f, //Sustain 1.0f, 2.0f, }, { 0.0f, 0.0f, //Release 0.5f, 1.0f, } }; //Slow start to fast end attack envelopePoint_t envEndAttack[] = { { 0.0f, 0.1f, 0.5f, 1.0f, }, { -1.0f, -1.0f, 0.5f, 1.0f, }, { 0.3f, 0.5f, 0.2f, 0.5f, }, { 0.0f, 0.0f, 0.5f, 1.0f, }, }; //rise, long sustain, release envelopePoint_t envMidSustain[] = { { 0.2f, 0.4f, 0.1f, 0.5f, }, { -1.0f, -1.0f, 0.1f, 0.5f, }, { 0.0f, 0.0f, 0.5f, 1.0f, }, }; //Scared envelopePoint_t envScared[] = { { 0.8f, 1.0f, 0.1f, 0.2f, }, { -1.0f, -1.0f, 0.25f, 0.5f }, { 0.0f, 0.0f, 0.5f, 1.0f, }, }; //Grub voice envelopes envelopeDescription_t grubVoiceEnvelopes[] = { { envFastAttack, ARRAYSIZE(envFastAttack) }, { envEndAttack, ARRAYSIZE(envEndAttack) }, { envMidSustain, ARRAYSIZE(envMidSustain) }, }; //Data description BEGIN_DATADESC( CNPC_AntlionGrub ) //DEFINE_SOUNDPATCH( m_pMovementSound ), //DEFINE_SOUNDPATCH( m_pVoiceSound ), //DEFINE_SOUNDPATCH( m_pHealSound ), DEFINE_FIELD( m_pGlowSprite, FIELD_CLASSPTR ), DEFINE_FIELD( m_flNextVoiceChange, FIELD_TIME ), DEFINE_FIELD( m_flSquashTime, FIELD_TIME ), DEFINE_FIELD( m_flNearTime, FIELD_TIME ), DEFINE_FIELD( m_flHealthTime, FIELD_TIME ), DEFINE_FIELD( m_flEnemyHostileTime, FIELD_TIME ), DEFINE_FIELD( m_bSquashed, FIELD_BOOLEAN ), DEFINE_FIELD( m_bMoving, FIELD_BOOLEAN ), DEFINE_FIELD( m_bSquashValid, FIELD_BOOLEAN ), DEFINE_FIELD( m_bHealing, FIELD_BOOLEAN ), DEFINE_FIELD( m_nHealthReserve, FIELD_INTEGER ), DEFINE_FIELD( m_nGlowSpriteHandle, FIELD_INTEGER ), // Functions DEFINE_ENTITYFUNC( GrubTouch ), END_DATADESC() //Schedules enum AntlionGrubSchedules { SCHED_ANTLIONGRUB_SQUEAL = LAST_SHARED_SCHEDULE, SCHED_ANTLIONGRUB_SQUIRM, SCHED_ANTLIONGRUB_STAND, SCHED_ANTLIONGRUB_GIVE_HEALTH, SCHED_ANTLIONGUARD_RETREAT, }; //Tasks enum AntlionGrubTasks { TASK_ANTLIONGRUB_SQUIRM = LAST_SHARED_TASK, TASK_ANTLIONGRUB_GIVE_HEALTH, TASK_ANTLIONGRUB_MOVE_TO_TARGET, TASK_ANTLIONGRUB_FIND_RETREAT_GOAL, }; //Conditions enum AntlionGrubConditions { COND_ANTLIONGRUB_HEARD_SQUEAL = LAST_SHARED_CONDITION, COND_ANTLIONGRUB_BEING_SQUASHED, COND_ANTLIONGRUB_IN_HEAL_RANGE, }; //Activities int ACT_ANTLIONGRUB_SQUIRM; int ACT_ANTLIONGRUB_HEAL; //Animation events #define ANTLIONGRUB_AE_START_SQUIRM 11 //Start squirming portion of animation #define ANTLIONGRUB_AE_END_SQUIRM 12 //End squirming portion of animation //Interaction IDs int g_interactionAntlionGrubAlert = 0; #define REGISTER_INTERACTION( a ) { a = CBaseCombatCharacter::GetInteractionID(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CNPC_AntlionGrub::CNPC_AntlionGrub( void ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::InitCustomSchedules( void ) { INIT_CUSTOM_AI( CNPC_AntlionGrub ); //Schedules ADD_CUSTOM_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_SQUEAL ); ADD_CUSTOM_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_SQUIRM ); ADD_CUSTOM_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_STAND ); ADD_CUSTOM_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_GIVE_HEALTH ); ADD_CUSTOM_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGUARD_RETREAT ); //Tasks ADD_CUSTOM_TASK( CNPC_AntlionGrub, TASK_ANTLIONGRUB_SQUIRM ); ADD_CUSTOM_TASK( CNPC_AntlionGrub, TASK_ANTLIONGRUB_GIVE_HEALTH ); ADD_CUSTOM_TASK( CNPC_AntlionGrub, TASK_ANTLIONGRUB_MOVE_TO_TARGET ); ADD_CUSTOM_TASK( CNPC_AntlionGrub, TASK_ANTLIONGRUB_FIND_RETREAT_GOAL ); //Conditions ADD_CUSTOM_CONDITION( CNPC_AntlionGrub, COND_ANTLIONGRUB_HEARD_SQUEAL ); ADD_CUSTOM_CONDITION( CNPC_AntlionGrub, COND_ANTLIONGRUB_BEING_SQUASHED ); ADD_CUSTOM_CONDITION( CNPC_AntlionGrub, COND_ANTLIONGRUB_IN_HEAL_RANGE ); //Activities ADD_CUSTOM_ACTIVITY( CNPC_AntlionGrub, ACT_ANTLIONGRUB_SQUIRM ); ADD_CUSTOM_ACTIVITY( CNPC_AntlionGrub, ACT_ANTLIONGRUB_HEAL ); AI_LOAD_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_SQUEAL ); AI_LOAD_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_SQUIRM ); AI_LOAD_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_STAND ); AI_LOAD_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGRUB_GIVE_HEALTH ); AI_LOAD_SCHEDULE( CNPC_AntlionGrub, SCHED_ANTLIONGUARD_RETREAT ); } LINK_ENTITY_TO_CLASS( npc_antlion_grub, CNPC_AntlionGrub ); IMPLEMENT_CUSTOM_AI( npc_antlion_grub, CNPC_AntlionGrub ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::Precache( void ) { PrecacheModel( ANTLIONGRUB_MODEL ); PrecacheModel( ANTLIONGRUB_SQUASHED_MODEL ); m_nGlowSpriteHandle = PrecacheModel("sprites/blueflare1.vmt"); /* PrecacheScriptSound( "NPC_AntlionGrub.Scared" ); PrecacheScriptSound( "NPC_AntlionGrub.Squash" ); PrecacheScriptSound( "NPC_Antlion.Movement" ); PrecacheScriptSound( "NPC_Antlion.Voice" ); PrecacheScriptSound( "NPC_Antlion.Heal" ); */ BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: Attaches the grub to the surface underneath its abdomen //----------------------------------------------------------------------------- void CNPC_AntlionGrub::AttachToSurface( void ) { // Get our downward direction Vector vecForward, vecRight, vecDown; GetVectors( &vecForward, &vecRight, &vecDown ); vecDown.Negate(); // Trace down to find a surface trace_t tr; UTIL_TraceLine( WorldSpaceCenter(), WorldSpaceCenter() + (vecDown*256.0f), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction < 1.0f ) { // Move there UTIL_SetOrigin( this, tr.endpos, false ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::Spawn( void ) { Precache(); SetModel( ANTLIONGRUB_MODEL ); m_NPCState = NPC_STATE_NONE; m_iHealth = sk_antliongrub_health.GetFloat(); m_iMaxHealth = m_iHealth; m_flFieldOfView = 0.5f; SetSolid( SOLID_BBOX ); SetMoveType( MOVETYPE_NONE ); SetCollisionGroup( COLLISION_GROUP_DEBRIS ); SetHullSizeNormal(); SetHullType( HULL_SMALL_CENTERED ); SetBloodColor( BLOOD_COLOR_YELLOW ); CapabilitiesClear(); m_flNextVoiceChange = gpGlobals->curtime; m_flSquashTime = gpGlobals->curtime; m_flNearTime = gpGlobals->curtime; m_flHealthTime = gpGlobals->curtime; m_flEnemyHostileTime = gpGlobals->curtime; m_bMoving = false; m_bSquashed = false; m_bSquashValid = false; m_bHealing = false; m_nHealthReserve = 10; SetTouch( GrubTouch ); // Attach to the surface under our belly AttachToSurface(); // Use detailed collision because we're an odd shape CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES ); /* CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); CPASAttenuationFilter filter( this ); m_pMovementSound = controller.SoundCreate( filter, entindex(), CHAN_BODY, "NPC_Antlion.Movement", 3.9f ); m_pVoiceSound = controller.SoundCreate( filter, entindex(), CHAN_VOICE, "NPC_Antlion.Voice", 3.9f ); m_pHealSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, "NPC_Antlion.Heal", 3.9f ); controller.Play( m_pMovementSound, 0.0f, 100 ); controller.Play( m_pVoiceSound, 0.0f, 100 ); controller.Play( m_pHealSound, 0.0f, 100 ); */ m_pGlowSprite = CSprite::SpriteCreate( "sprites/blueflare1.vmt", GetLocalOrigin(), false ); Assert( m_pGlowSprite ); if ( m_pGlowSprite == NULL ) return; Vector vecUp; GetVectors( NULL, NULL, &vecUp ); m_pGlowSprite->TurnOn(); m_pGlowSprite->SetTransparency( kRenderWorldGlow, 156, 169, 121, 164, kRenderFxNoDissipation ); m_pGlowSprite->SetAbsOrigin( GetAbsOrigin() + vecUp * 8.0f ); m_pGlowSprite->SetScale( 1.0f ); m_pGlowSprite->SetGlowProxySize( 16.0f ); // We don't want to teleport at this point AddSpawnFlags( SF_NPC_FALL_TO_GROUND ); // We get a bogus error otherwise NPCInit(); AddSolidFlags( FSOLID_TRIGGER ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pEvent - //----------------------------------------------------------------------------- void CNPC_AntlionGrub::HandleAnimEvent( animevent_t *pEvent ) { switch ( pEvent->event ) { case ANTLIONGRUB_AE_START_SQUIRM: { //float duration = random->RandomFloat( 0.1f, 0.3f ); //CSoundEnvelopeController::GetController().SoundChangePitch( m_pMovementSound, random->RandomInt( 100, 120 ), duration ); //CSoundEnvelopeController::GetController().SoundChangeVolume( m_pMovementSound, random->RandomFloat( 0.6f, 0.8f ), duration ); } break; case ANTLIONGRUB_AE_END_SQUIRM: { //float duration = random->RandomFloat( 0.1f, 0.3f ); //CSoundEnvelopeController::GetController().SoundChangePitch( m_pMovementSound, random->RandomInt( 80, 100 ), duration ); //CSoundEnvelopeController::GetController().SoundChangeVolume( m_pMovementSound, random->RandomFloat( 0.0f, 0.1f ), duration ); } break; default: BaseClass::HandleAnimEvent( pEvent ); return; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CNPC_AntlionGrub::SelectSchedule( void ) { //If we've heard someone else squeal, we should too if ( HasCondition( COND_ANTLIONGRUB_HEARD_SQUEAL ) || HasCondition( COND_ANTLIONGRUB_BEING_SQUASHED ) ) { m_flEnemyHostileTime = gpGlobals->curtime + ANTLIONGRUB_ENEMY_HOSTILE_TIME; return SCHED_SMALL_FLINCH; } //See if we need to run away from our enemy /* if ( m_flEnemyHostileTime > gpGlobals->curtime ) return SCHED_ANTLIONGUARD_RETREAT; */ /* if ( HasCondition( COND_ANTLIONGRUB_IN_HEAL_RANGE ) ) { SetTarget( GetEnemy() ); return SCHED_ANTLIONGRUB_GIVE_HEALTH; } */ //If we've taken any damage, squirm and squeal if ( HasCondition( COND_LIGHT_DAMAGE ) && SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 ) return SCHED_SMALL_FLINCH; /* //Randomly stand still if ( random->RandomInt( 0, 3 ) == 0 ) return SCHED_IDLE_STAND; //Otherwise just walk around a little return SCHED_PATROL_WALK; */ return SCHED_IDLE_STAND; } //----------------------------------------------------------------------------- // Purpose: // Input : *pTask - //----------------------------------------------------------------------------- void CNPC_AntlionGrub::StartTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_ANTLIONGRUB_FIND_RETREAT_GOAL: { if ( GetEnemy() == NULL ) { TaskFail( FAIL_NO_ENEMY ); return; } Vector testPos, testPos2, threatDir; trace_t tr; //Find the direction to our enemy threatDir = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ); VectorNormalize( threatDir ); //Find a position farther out away from our enemy VectorMA( GetAbsOrigin(), random->RandomInt( 32, 128 ), threatDir, testPos ); testPos[2] += StepHeight()*2.0f; testPos2 = testPos; testPos2[2] -= StepHeight()*2.0f; //Check the position AI_TraceLine( testPos, testPos2, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); //Must be clear if ( ( tr.startsolid ) || ( tr.allsolid ) || ( tr.fraction == 1.0f ) ) { TaskFail( FAIL_NO_ROUTE ); return; } //Save the position and go m_vSavePosition = tr.endpos; TaskComplete(); } break; case TASK_ANTLIONGRUB_MOVE_TO_TARGET: if ( GetEnemy() == NULL) { TaskFail( FAIL_NO_TARGET ); } else if ( ( GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length() < pTask->flTaskData ) { TaskComplete(); } break; case TASK_ANTLIONGRUB_GIVE_HEALTH: m_bHealing = true; SetActivity( (Activity) ACT_ANTLIONGRUB_HEAL ); //CSoundEnvelopeController::GetController().SoundChangeVolume( m_pHealSound, 0.5f, 2.0f ); //Must have a target if ( GetEnemy() == NULL ) { TaskFail( FAIL_NO_ENEMY ); return; } //Must be within range if ( (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length() > 92 ) { TaskFail( FAIL_NO_ENEMY ); } break; case TASK_ANTLIONGRUB_SQUIRM: { //Pick a squirm movement to perform Vector vecStart; //Move randomly around, and start a step's height above our current position vecStart.Random( -32.0f, 32.0f ); vecStart[2] = StepHeight(); vecStart += GetLocalOrigin(); //Look straight down for the ground Vector vecEnd = vecStart; vecEnd[2] -= StepHeight()*2.0f; trace_t tr; //Check the position //FIXME: Trace by the entity's hull size? AI_TraceLine( vecStart, vecEnd, MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); //See if we can move there if ( ( tr.fraction == 1.0f ) || ( tr.startsolid ) || ( tr.allsolid ) ) { TaskFail( FAIL_NO_ROUTE ); return; } m_vSavePosition = tr.endpos; TaskComplete(); } break; default: BaseClass::StartTask( pTask ); break; } } //----------------------------------------------------------------------------- // Purpose: // Input : *pTask - //----------------------------------------------------------------------------- void CNPC_AntlionGrub::RunTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_ANTLIONGRUB_MOVE_TO_TARGET: { //Must have a target entity if ( GetEnemy() == NULL ) { TaskFail( FAIL_NO_TARGET ); return; } float distance = ( GetNavigator()->GetGoalPos() - GetLocalOrigin() ).Length2D(); if ( ( GetNavigator()->GetGoalPos() - GetEnemy()->GetLocalOrigin() ).Length() > (pTask->flTaskData * 0.5f) ) { distance = ( GetEnemy()->GetLocalOrigin() - GetLocalOrigin() ).Length2D(); GetNavigator()->UpdateGoalPos( GetEnemy()->GetLocalOrigin() ); } //See if we've arrived if ( distance < pTask->flTaskData ) { TaskComplete(); GetNavigator()->StopMoving(); } } break; case TASK_ANTLIONGRUB_GIVE_HEALTH: //Validate the enemy if ( GetEnemy() == NULL ) { TaskFail( FAIL_NO_ENEMY ); return; } //Are we done giving health? if ( ( GetEnemy()->m_iHealth == GetEnemy()->m_iMaxHealth ) || ( m_nHealthReserve <= 0 ) || ( (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length() > 64 ) ) { m_bHealing = false; //CSoundEnvelopeController::GetController().SoundChangeVolume( m_pHealSound, 0.0f, 0.5f ); TaskComplete(); return; } //Is it time to heal again? if ( m_flHealthTime < gpGlobals->curtime ) { m_flHealthTime = gpGlobals->curtime + 0.5f; //Update the health if ( GetEnemy()->m_iHealth < GetEnemy()->m_iMaxHealth ) { GetEnemy()->m_iHealth++; m_nHealthReserve--; } } break; default: BaseClass::RunTask( pTask ); break; } } #define TRANSLATE_SCHEDULE( type, in, out ) { if ( type == in ) return out; } //----------------------------------------------------------------------------- // Purpose: override/translate a schedule by type // Input : Type - schedule type // Output : int - translated type //----------------------------------------------------------------------------- int CNPC_AntlionGrub::TranslateSchedule( int type ) { TRANSLATE_SCHEDULE( type, SCHED_IDLE_STAND, SCHED_ANTLIONGRUB_STAND ); TRANSLATE_SCHEDULE( type, SCHED_PATROL_WALK,SCHED_ANTLIONGRUB_SQUIRM ); return BaseClass::TranslateSchedule( type ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pOther - //----------------------------------------------------------------------------- void CNPC_AntlionGrub::GrubTouch( CBaseEntity *pOther ) { //Don't consider the world if ( FClassnameIs( pOther, "worldspawn" ) ) return; //Allow a crusing velocity to kill them in one go (or they're already dead) if ( ( pOther->GetAbsVelocity().Length() > 200 ) || ( IsAlive() == false ) ) { //TakeDamage( CTakeDamageInfo( pOther, pOther, vec3_origin, GetAbsOrigin(), 100, DMG_CRUSH ) ); return; } //Need to know we're being squashed SetCondition( COND_ANTLIONGRUB_BEING_SQUASHED ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pOther - //----------------------------------------------------------------------------- void CNPC_AntlionGrub::EndTouch( CBaseEntity *pOther ) { ClearCondition( COND_ANTLIONGRUB_BEING_SQUASHED ); m_bSquashValid = false; /* CSoundEnvelopeController::GetController().SoundChangePitch( m_pVoiceSound, 100, 0.5f ); CSoundEnvelopeController::GetController().SoundChangeVolume( m_pVoiceSound, 0.0f, 1.0f ); */ m_flPlaybackRate = 1.0f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::BroadcastAlert( void ) { /* CBaseEntity *pEntity = NULL; CAI_BaseNPC *pNPC; //Look in a radius for potential listeners for ( CEntitySphereQuery sphere( GetAbsOrigin(), ANTLIONGRUB_SQUEAL_DIST ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() ) { if ( !( pEntity->GetFlags() & FL_NPC ) ) continue; pNPC = pEntity->MyNPCPointer(); //Only antlions care if ( pNPC->Classify() == CLASS_ANTLION ) { pNPC->DispatchInteraction( g_interactionAntlionGrubAlert, NULL, this ); } } */ } //----------------------------------------------------------------------------- // Purpose: Twiddle damage based on certain criteria //----------------------------------------------------------------------------- int CNPC_AntlionGrub::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { CTakeDamageInfo newInfo = info; // Always squash on a crowbar hit if ( newInfo.GetDamageType() & DMG_CLUB ) { newInfo.SetDamage( GetHealth() + 1.0f ); } return BaseClass::OnTakeDamage_Alive( newInfo ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::PainSound( const CTakeDamageInfo &info ) { BroadcastAlert(); } //----------------------------------------------------------------------------- // Purpose: // Input : interactionType - // *data - // *sourceEnt - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_AntlionGrub::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sourceEnt ) { //Handle squeals from our peers if ( interactionType == g_interactionAntlionGrubAlert ) { SetCondition( COND_ANTLIONGRUB_HEARD_SQUEAL ); //float envDuration = PlayEnvelope( m_pVoiceSound, SOUNDCTRL_CHANGE_VOLUME, envScared, ARRAYSIZE(envScared) ); //float envDuration = CSoundEnvelopeController::GetController().SoundPlayEnvelope( m_pVoiceSound, SOUNDCTRL_CHANGE_VOLUME, envMidSustain, ARRAYSIZE(envMidSustain) ); //m_flNextVoiceChange = gpGlobals->curtime + envDuration + random->RandomFloat( 4.0f, 8.0f ); return true; } return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::Squash( CBaseEntity *pOther ) { SpawnSquashedGrub(); AddEffects( EF_NODRAW ); AddSolidFlags( FSOLID_NOT_SOLID ); EmitSound( "NPC_AntlionGrub.Squash" ); BroadcastAlert(); Vector vecUp; AngleVectors( GetAbsAngles(), NULL, NULL, &vecUp ); trace_t tr; for ( int i = 0; i < 4; i++ ) { tr.endpos = WorldSpaceCenter(); tr.endpos[0] += random->RandomFloat( -16.0f, 16.0f ); tr.endpos[1] += random->RandomFloat( -16.0f, 16.0f ); tr.endpos += vecUp * 8.0f; MakeDamageBloodDecal( 2, 0.8f, &tr, -vecUp ); } SetTouch( NULL ); m_bSquashed = true; // Temp squash effect CEffectData data; data.m_fFlags = 0; data.m_vOrigin = WorldSpaceCenter(); data.m_vNormal = vecUp; VectorAngles( vecUp, data.m_vAngles ); data.m_flScale = random->RandomFloat( 5, 7 ); data.m_fFlags |= FX_WATER_IN_SLIME; DispatchEffect( "watersplash", data ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::SpawnSquashedGrub( void ) { CGib *pStandin = CREATE_ENTITY( CGib, "gib" ); Assert( pStandin ); pStandin->SetModel( ANTLIONGRUB_SQUASHED_MODEL ); pStandin->AddSolidFlags( FSOLID_NOT_SOLID ); pStandin->SetLocalAngles( GetLocalAngles() ); pStandin->SetLocalOrigin( GetLocalOrigin() ); } void CNPC_AntlionGrub::StopLoopingSounds() { /* CSoundEnvelopeController::GetController().SoundDestroy( m_pMovementSound ); CSoundEnvelopeController::GetController().SoundDestroy( m_pVoiceSound ); CSoundEnvelopeController::GetController().SoundDestroy( m_pHealSound ); m_pMovementSound = NULL; m_pVoiceSound = NULL; m_pHealSound = NULL; */ BaseClass::StopLoopingSounds(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::Event_Killed( const CTakeDamageInfo &info ) { BaseClass::Event_Killed( info ); if ( ( m_bSquashed == false ) && ( info.GetDamageType() & DMG_CLUB ) ) { // Die! Squash( info.GetAttacker() ); } else { //Restore this touch so we can still be squished SetTouch( GrubTouch ); } // Slowly fade out glow out m_pGlowSprite->FadeAndDie( 5.0f ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_AntlionGrub::PrescheduleThink( void ) { BaseClass::PrescheduleThink(); //Add a fail-safe for the glow sprite display if ( !IsCurSchedule( SCHED_ANTLIONGRUB_GIVE_HEALTH ) ) { if ( m_bHealing ) { //CSoundEnvelopeController::GetController().SoundChangeVolume( m_pHealSound, 0.0f, 0.5f ); } m_bHealing = false; } //Do glowing maintenance /* if ( m_pGlowSprite != NULL ) { m_pGlowSprite->SetLocalOrigin( GetLocalOrigin() ); if ( m_pGlowSprite->IsEffectActive( EF_NODRAW ) == false ) { m_pGlowSprite->SetScale( random->RandomFloat( 0.75f, 1.0f ) ); float scale = random->RandomFloat( 0.25f, 0.75f ); m_pGlowSprite->SetTransparency( kRenderGlow, (int)(32.0f*scale), (int)(32.0f*scale), (int)(128.0f*scale), 255, kRenderFxNoDissipation ); } //Deal with the healing glow if ( m_bHealing ) { m_pGlowSprite->TurnOn(); } else { m_pGlowSprite->TurnOff(); } } */ //Check for movement sounds if ( m_flGroundSpeed > 0.0f ) { if ( m_bMoving == false ) { //CSoundEnvelopeController::GetController().SoundChangePitch( m_pMovementSound, 100, 0.1f ); //CSoundEnvelopeController::GetController().SoundChangeVolume( m_pMovementSound, 0.4f, 1.0f ); m_bMoving = true; } } else if ( m_bMoving ) { //CSoundEnvelopeController::GetController().SoundChangePitch( m_pMovementSound, 80, 0.5f ); //CSoundEnvelopeController::GetController().SoundChangeVolume( m_pMovementSound, 0.0f, 1.0f ); m_bMoving = false; } //Check for a voice change if ( m_flNextVoiceChange < gpGlobals->curtime ) { //float envDuration = CSoundEnvelopeController::GetController().SoundPlayEnvelope( m_pVoiceSound, SOUNDCTRL_CHANGE_VOLUME, &grubVoiceEnvelopes[rand()%ARRAYSIZE(grubVoiceEnvelopes)] ); //m_flNextVoiceChange = gpGlobals->curtime + envDuration + random->RandomFloat( 1.0f, 8.0f ); } } //----------------------------------------------------------------------------- // Purpose: // Input : flDot - // flDist - // Output : int //----------------------------------------------------------------------------- int CNPC_AntlionGrub::MeleeAttack1Conditions( float flDot, float flDist ) { ClearCondition( COND_ANTLIONGRUB_IN_HEAL_RANGE ); //If we're outside the heal range, then reset our timer if ( flDist > ANTLIONGRUB_HEAL_RANGE ) { m_flNearTime = gpGlobals->curtime + 2.0f; return COND_TOO_FAR_TO_ATTACK; } //Otherwise if we've been in range for long enough signal it if ( m_flNearTime < gpGlobals->curtime ) { if ( ( m_nHealthReserve > 0 ) && ( GetEnemy()->m_iHealth < GetEnemy()->m_iMaxHealth ) ) { SetCondition( COND_ANTLIONGRUB_IN_HEAL_RANGE ); } } return COND_CAN_MELEE_ATTACK1; } //----------------------------------------------------------------------------- // Purpose: Allows for modification of the interrupt mask for the current schedule. // In the most cases the base implementation should be called first. //----------------------------------------------------------------------------- void CNPC_AntlionGrub::BuildScheduleTestBits( void ) { //Always squirm if we're being squashed if ( !IsCurSchedule( SCHED_SMALL_FLINCH ) ) { SetCustomInterruptCondition( COND_ANTLIONGRUB_BEING_SQUASHED ); } } //----------------------------------------------------------------------------- // // Schedules // //----------------------------------------------------------------------------- //================================================== // SCHED_ANTLIONGRUB_SQUEAL //================================================== AI_DEFINE_SCHEDULE ( SCHED_ANTLIONGRUB_SQUEAL, " Tasks" " TASK_FACE_ENEMY 0" " " " Interrupts" " COND_ANTLIONGRUB_BEING_SQUASHED " " COND_NEW_ENEMY" ); //================================================== // SCHED_ANTLIONGRUB_STAND //================================================== AI_DEFINE_SCHEDULE ( SCHED_ANTLIONGRUB_STAND, " Tasks" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_IDLE" " " " Interrupts" " COND_LIGHT_DAMAGE" " COND_ANTLIONGRUB_HEARD_SQUEAL" " COND_ANTLIONGRUB_BEING_SQUASHED" " COND_NEW_ENEMY" ); //================================================== // SCHED_ANTLIONGRUB_SQUIRM //================================================== AI_DEFINE_SCHEDULE ( SCHED_ANTLIONGRUB_SQUIRM, " Tasks" " TASK_ANTLIONGRUB_SQUIRM 0" " TASK_SET_GOAL GOAL:SAVED_POSITION" " TASK_GET_PATH_TO_GOAL PATH:TRAVEL" " TASK_WALK_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " " " Interrupts" " COND_TASK_FAILED" " COND_LIGHT_DAMAGE" " COND_ANTLIONGRUB_HEARD_SQUEAL" " COND_ANTLIONGRUB_BEING_SQUASHED" " COND_NEW_ENEMY" ); //================================================== // SCHED_ANTLIONGRUB_GIVE_HEALTH //================================================== AI_DEFINE_SCHEDULE ( SCHED_ANTLIONGRUB_GIVE_HEALTH, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGRUB_STAND" " TASK_STOP_MOVING 0" " TASK_SET_GOAL GOAL:ENEMY" " TASK_GET_PATH_TO_GOAL PATH:TRAVEL" " TASK_RUN_PATH 0" " TASK_ANTLIONGRUB_MOVE_TO_TARGET 48" " TASK_STOP_MOVING 0" " TASK_FACE_ENEMY 0" " TASK_ANTLIONGRUB_GIVE_HEALTH 0" " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLIONGRUB_SQUIRM" " " " Interrupts" " COND_TASK_FAILED" " COND_NEW_ENEMY" " COND_ANTLIONGRUB_BEING_SQUASHED" " COND_ANTLIONGRUB_HEARD_SQUEAL" ); //================================================== // SCHED_ANTLIONGUARD_RETREAT //================================================== AI_DEFINE_SCHEDULE ( SCHED_ANTLIONGUARD_RETREAT, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ANTLIONGRUB_STAND" " TASK_STOP_MOVING 0" " TASK_ANTLIONGRUB_FIND_RETREAT_GOAL 0" " TASK_SET_GOAL GOAL:SAVED_POSITION" " TASK_GET_PATH_TO_GOAL PATH:TRAVEL" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_SET_SCHEDULE SCHEDULE:SCHED_ANTLIONGRUB_STAND" " " " Interrupts" " COND_TASK_FAILED" " COND_NEW_ENEMY" " COND_ANTLIONGRUB_BEING_SQUASHED" " COND_ANTLIONGRUB_HEARD_SQUEAL" );
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 5821 | Knut Wikstrom |
Added Valve Source code. This is NOT to be commited to other than new code from Valve. |