void CMTalkMonster::StartFollowing( edict_t *pLeader ) { if ( m_hEnemy != NULL ) m_IdealMonsterState = MONSTERSTATE_ALERT; m_hTargetEnt = pLeader; PlaySentence( m_szGrp[TLK_USE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); m_hTalkTarget = m_hTargetEnt; ClearConditions( bits_COND_CLIENT_PUSH ); ClearSchedule(); }
void CTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, const bool bConcurrent, CBaseEntity *pListener ) { if ( !bConcurrent ) ShutUpFriends(); ClearConditions( bits_COND_CLIENT_PUSH ); // Forget about moving! I've got something to say! m_useTime = gpGlobals->time + duration; PlaySentence( pszSentence, duration, volume, attenuation ); m_hTalkTarget = pListener; }
//========================================================= // Look - overriden for the roach, which can virtually see // 360 degrees. //========================================================= void CRoach :: Look ( int iDistance ) { CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with CBaseEntity *pPreviousEnt;// the last entity added to the link list int iSighted = 0; // DON'T let visibility information from last frame sit around! ClearConditions( bits_COND_SEE_HATE |bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR ); // don't let monsters outside of the player's PVS act up, or most of the interesting // things will happen before the player gets there! if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) { return; } m_pLink = NULL; pPreviousEnt = this; // Does sphere also limit itself to PVS? // Examine all entities within a reasonable radius // !!!PERFORMANCE - let's trivially reject the ent list before radius searching! while ((pSightEnt = UTIL_FindEntityInSphere( pSightEnt, pev->origin, iDistance )) != NULL) { // only consider ents that can be damaged. !!!temporarily only considering other monsters and clients if ( pSightEnt->IsPlayer() || FBitSet ( pSightEnt->pev->flags, FL_MONSTER ) ) { if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && pSightEnt->pev->health > 0 ) { // NULL the Link pointer for each ent added to the link list. If other ents follow, the will overwrite // this value. If this ent happens to be the last, the list will be properly terminated. pPreviousEnt->m_pLink = pSightEnt; pSightEnt->m_pLink = NULL; pPreviousEnt = pSightEnt; // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when // we see monsters other than the Enemy. switch ( IRelationship ( pSightEnt ) ) { case R_FR: iSighted |= bits_COND_SEE_FEAR; break; case R_NO: break; default: ALERT ( at_console, "%s can't asses %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); break; } } } } SetConditions( iSighted ); }
//========================================================= // RunAI //========================================================= void CBaseMonster :: RunAI ( void ) { // to test model's eye height //UTIL_ParticleEffect ( pev->origin + pev->view_ofs, g_vecZero, 255, 10 ); // IDLE sound permitted in ALERT state is because monsters were silent in ALERT state. Only play IDLE sound in IDLE state // once we have sounds for that state. if ( ( m_MonsterState == MONSTERSTATE_IDLE || m_MonsterState == MONSTERSTATE_ALERT ) && RANDOM_LONG(0,99) == 0 && !(pev->flags & SF_MONSTER_GAG) ) { IdleSound(); } if ( m_MonsterState != MONSTERSTATE_NONE && m_MonsterState != MONSTERSTATE_PRONE && m_MonsterState != MONSTERSTATE_DEAD )// don't bother with this crap if monster is prone. { // collect some sensory Condition information. // don't let monsters outside of the player's PVS act up, or most of the interesting // things will happen before the player gets there! // UPDATE: We now let COMBAT state monsters think and act fully outside of player PVS. This allows the player to leave // an area where monsters are fighting, and the fight will continue. if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) || ( m_MonsterState == MONSTERSTATE_COMBAT ) ) { Look( m_flDistLook ); Listen();// check for audible sounds. // now filter conditions. ClearConditions( IgnoreConditions() ); GetEnemy(); } // do these calculations if monster has an enemy. if ( m_hEnemy != NULL ) { CheckEnemy( m_hEnemy ); } CheckAmmo(); } FCheckAITrigger(); PrescheduleThink(); MaintainSchedule(); // if the monster didn't use these conditions during the above call to MaintainSchedule() or CheckAITrigger() // we throw them out cause we don't want them sitting around through the lifespan of a schedule // that doesn't use them. m_afConditions &= ~( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ); }
void CBaseMonster::Look(int iDistance) { int iSighted = 0; ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); m_pLink = NULL; CBaseEntity *pSightEnt = NULL; CBaseEntity *pList[100]; Vector delta = Vector(iDistance, iDistance, iDistance); int count = UTIL_EntitiesInBox(pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT | FL_MONSTER); for (int i = 0; i < count; i++) { pSightEnt = pList[i]; if (pSightEnt != this && pSightEnt->pev->health > 0) { if (IRelationship(pSightEnt) != R_NO && FInViewCone(pSightEnt) && !FBitSet(pSightEnt->pev->flags, FL_NOTARGET) && FVisible(pSightEnt)) { if (pSightEnt->IsPlayer()) iSighted |= bits_COND_SEE_CLIENT; pSightEnt->m_pLink = m_pLink; m_pLink = pSightEnt; if (pSightEnt == m_hEnemy) iSighted |= bits_COND_SEE_ENEMY; switch (IRelationship(pSightEnt)) { case R_NM: iSighted |= bits_COND_SEE_NEMESIS; break; case R_HT: iSighted |= bits_COND_SEE_HATE; break; case R_DL: iSighted |= bits_COND_SEE_DISLIKE; break; case R_FR: iSighted |= bits_COND_SEE_FEAR; break; case R_AL: break; } } } } SetConditions(iSighted); }
void CZombie::GatherConditions( void ) { BaseClass::GatherConditions(); static int conditionsToClear[] = { COND_BLOCKED_BY_DOOR, COND_DOOR_OPENED, COND_ZOMBIE_CHARGE_TARGET_MOVED, }; ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); if ( m_hBlockingDoor == NULL || ( m_hBlockingDoor->m_toggle_state == TS_AT_TOP || m_hBlockingDoor->m_toggle_state == TS_GOING_UP ) ) { ClearCondition( COND_BLOCKED_BY_DOOR ); if ( m_hBlockingDoor != NULL ) { SetCondition( COND_DOOR_OPENED ); m_hBlockingDoor = NULL; } } else SetCondition( COND_BLOCKED_BY_DOOR ); if ( ConditionInterruptsCurSchedule( COND_ZOMBIE_CHARGE_TARGET_MOVED ) ) { if ( GetNavigator()->IsGoalActive() ) { const float CHARGE_RESET_TOLERANCE = 60.0; if ( !GetEnemy() || ( m_vPositionCharged - GetEnemyLKP() ).Length() > CHARGE_RESET_TOLERANCE ) { SetCondition( COND_ZOMBIE_CHARGE_TARGET_MOVED ); } } } }
//========================================================= // OnListened - monsters dig through the active sound list for // any sounds that may interest them. (smells, too!) //========================================================= void CNPC_Bullsquid::OnListened( void ) { AISoundIter_t iter; CSound *pCurrentSound; static int conditionsToClear[] = { COND_SQUID_SMELL_FOOD, }; ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); pCurrentSound = GetSenses()->GetFirstHeardSound( &iter ); while ( pCurrentSound ) { // the npc cares about this sound, and it's close enough to hear. int condition = COND_NONE; if ( !pCurrentSound->FIsSound() ) { // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent if ( pCurrentSound->IsSoundType( SOUND_MEAT | SOUND_CARCASS ) ) { // the detected scent is a food item condition = COND_SQUID_SMELL_FOOD; } } if ( condition != COND_NONE ) SetCondition( condition ); pCurrentSound = GetSenses()->GetNextHeardSound( &iter ); } BaseClass::OnListened(); }
// // TentacleThink // void CTentacle :: Cycle( void ) { // ALERT( at_console, "%s %.2f %d %d\n", STRING( pev->targetname ), pev->origin.z, m_MonsterState, m_IdealMonsterState ); pev->nextthink = gpGlobals-> time + 0.1; // ALERT( at_console, "%s %d %d %d %f %f\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim, m_iDir, pev->framerate, pev->health ); if (m_MonsterState == MONSTERSTATE_SCRIPT || m_IdealMonsterState == MONSTERSTATE_SCRIPT) { pev->angles.y = m_flInitialYaw; pev->ideal_yaw = m_flInitialYaw; ClearConditions( IgnoreConditions() ); MonsterThink( ); m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; return; } DispatchAnimEvents( ); StudioFrameAdvance( ); ChangeYaw( pev->yaw_speed ); CSound *pSound; Listen( ); // Listen will set this if there's something in my sound list if ( HasConditions( bits_COND_HEAR_SOUND ) ) pSound = PBestSound(); else pSound = NULL; if ( pSound ) { Vector vecDir; if (gpGlobals->time - m_flPrevSoundTime < 0.5) { float dt = gpGlobals->time - m_flPrevSoundTime; vecDir = pSound->m_vecOrigin + (pSound->m_vecOrigin - m_vecPrevSound) / dt - pev->origin; } else { vecDir = pSound->m_vecOrigin - pev->origin; } m_flPrevSoundTime = gpGlobals->time; m_vecPrevSound = pSound->m_vecOrigin; m_flSoundYaw = UTIL_VecToYaw ( vecDir ) - m_flInitialYaw; m_iSoundLevel = Level( vecDir.z ); if (m_flSoundYaw < -180) m_flSoundYaw += 360; if (m_flSoundYaw > 180) m_flSoundYaw -= 360; // ALERT( at_console, "sound %d %.0f\n", m_iSoundLevel, m_flSoundYaw ); if (m_flSoundTime < gpGlobals->time) { // play "I hear new something" sound char *sound; switch( RANDOM_LONG(0,1) ) { case 0: sound = "tentacle/te_alert1.wav"; break; case 1: sound = "tentacle/te_alert2.wav"; break; } // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); } m_flSoundTime = gpGlobals->time + RANDOM_FLOAT( 5.0, 10.0 ); } // clip ideal_yaw float dy = m_flSoundYaw; switch( pev->sequence ) { case TENTACLE_ANIM_Floor_Rear: case TENTACLE_ANIM_Floor_Rear_Idle: case TENTACLE_ANIM_Lev1_Rear: case TENTACLE_ANIM_Lev1_Rear_Idle: case TENTACLE_ANIM_Lev2_Rear: case TENTACLE_ANIM_Lev2_Rear_Idle: case TENTACLE_ANIM_Lev3_Rear: case TENTACLE_ANIM_Lev3_Rear_Idle: if (dy < 0 && dy > -m_flMaxYaw) dy = -m_flMaxYaw; if (dy > 0 && dy < m_flMaxYaw) dy = m_flMaxYaw; break; default: if (dy < -m_flMaxYaw) dy = -m_flMaxYaw; if (dy > m_flMaxYaw) dy = m_flMaxYaw; } pev->ideal_yaw = m_flInitialYaw + dy; if (m_fSequenceFinished) { // ALERT( at_console, "%s done %d %d\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim ); if (pev->health <= 1) { m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; if (pev->sequence == TENTACLE_ANIM_Pit_Idle) { pev->health = 75; } } else if ( m_flSoundTime > gpGlobals->time ) { if (m_flSoundYaw >= -(m_flMaxYaw + 30) && m_flSoundYaw <= (m_flMaxYaw + 30)) { // strike m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); } else if (m_flSoundYaw >= -m_flMaxYaw * 2 && m_flSoundYaw <= m_flMaxYaw * 2) { // tap m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); } else { // go into rear idle m_iGoalAnim = LookupActivity( ACT_T_REARIDLE + m_iSoundLevel ); } } else if (pev->sequence == TENTACLE_ANIM_Pit_Idle) { // stay in pit until hear noise m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; } else if (pev->sequence == m_iGoalAnim) { if (MyLevel() >= 0 && gpGlobals->time < m_flSoundTime) { if (RANDOM_LONG(0,9) < m_flSoundTime - gpGlobals->time) { // continue stike m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); } else { // tap m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); } } else if (MyLevel( ) < 0) { m_iGoalAnim = LookupActivity( ACT_T_IDLE + 0 ); } else { if (m_flNextSong < gpGlobals->time) { // play "I hear new something" sound char *sound; switch( RANDOM_LONG(0,1) ) { case 0: sound = "tentacle/te_sing1.wav"; break; case 1: sound = "tentacle/te_sing2.wav"; break; } EMIT_SOUND(ENT(pev), CHAN_VOICE, sound, 1.0, ATTN_NORM); m_flNextSong = gpGlobals->time + RANDOM_FLOAT( 10, 20 ); } if (RANDOM_LONG(0,15) == 0) { // idle on new level m_iGoalAnim = LookupActivity( ACT_T_IDLE + RANDOM_LONG(0,3) ); } else if (RANDOM_LONG(0,3) == 0) { // tap m_iGoalAnim = LookupActivity( ACT_T_TAP + MyLevel( ) ); } else { // idle m_iGoalAnim = LookupActivity( ACT_T_IDLE + MyLevel( ) ); } } if (m_flSoundYaw < 0) m_flSoundYaw += RANDOM_FLOAT( 2, 8 ); else m_flSoundYaw -= RANDOM_FLOAT( 2, 8 ); } pev->sequence = FindTransition( pev->sequence, m_iGoalAnim, &m_iDir ); if (m_iDir > 0) { pev->frame = 0; } else { m_iDir = -1; // just to safe pev->frame = 255; } ResetSequenceInfo( ); m_flFramerateAdj = RANDOM_FLOAT( -0.2, 0.2 ); pev->framerate = m_iDir * 1.0 + m_flFramerateAdj; switch( pev->sequence) { case TENTACLE_ANIM_Floor_Tap: case TENTACLE_ANIM_Lev1_Tap: case TENTACLE_ANIM_Lev2_Tap: case TENTACLE_ANIM_Lev3_Tap: { Vector vecSrc; UTIL_MakeVectors( pev->angles ); TraceResult tr1, tr2; vecSrc = pev->origin + Vector( 0, 0, MyHeight() - 4); UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr1 ); vecSrc = pev->origin + Vector( 0, 0, MyHeight() + 8); UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr2 ); // ALERT( at_console, "%f %f\n", tr1.flFraction * 512, tr2.flFraction * 512 ); m_flTapRadius = SetBlending( 0, RANDOM_FLOAT( tr1.flFraction * 512, tr2.flFraction * 512 ) ); } break; default: m_flTapRadius = 336; // 400 - 64 break; } pev->view_ofs.z = MyHeight( ); // ALERT( at_console, "seq %d\n", pev->sequence ); } if (m_flPrevSoundTime + 2.0 > gpGlobals->time) { // 1.5 normal speed if hears sounds pev->framerate = m_iDir * 1.5 + m_flFramerateAdj; } else if (m_flPrevSoundTime + 5.0 > gpGlobals->time) { // slowdown to normal pev->framerate = m_iDir + m_iDir * (5 - (gpGlobals->time - m_flPrevSoundTime)) / 2 + m_flFramerateAdj; } }
//========================================================= // GetSchedule - Decides which type of schedule best suits // the monster's current state and conditions. Then calls // monster's member function to get a pointer to a schedule // of the proper type. //========================================================= Schedule_t *CBaseMonster :: GetSchedule ( void ) { switch ( m_MonsterState ) { case MONSTERSTATE_PRONE: { return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB ); break; } case MONSTERSTATE_NONE: { ALERT ( at_aiconsole, "MONSTERSTATE IS NONE!\n" ); break; } case MONSTERSTATE_IDLE: { if ( HasConditions ( bits_COND_HEAR_SOUND ) ) { return GetScheduleOfType( SCHED_ALERT_FACE ); } else if ( FRouteClear() ) { // no valid route! return GetScheduleOfType( SCHED_IDLE_STAND ); } else { // valid route. Get moving return GetScheduleOfType( SCHED_IDLE_WALK ); } break; } case MONSTERSTATE_ALERT: { if ( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE ) { return GetScheduleOfType ( SCHED_VICTORY_DANCE ); } if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) { if ( fabs( FlYawDiff() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction { return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN ); } else { return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH ); } } else if ( HasConditions ( bits_COND_HEAR_SOUND ) ) { return GetScheduleOfType( SCHED_ALERT_FACE ); } else { return GetScheduleOfType( SCHED_ALERT_STAND ); } break; } case MONSTERSTATE_COMBAT: { if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // clear the current (dead) enemy and try to find another. m_hEnemy = NULL; if ( GetEnemy() ) { ClearConditions( bits_COND_ENEMY_DEAD ); return GetSchedule(); } else { SetState( MONSTERSTATE_ALERT ); return GetSchedule(); } } if ( HasConditions(bits_COND_NEW_ENEMY) ) { return GetScheduleOfType ( SCHED_WAKE_ANGRY ); } else if (HasConditions(bits_COND_LIGHT_DAMAGE) && !HasMemory( bits_MEMORY_FLINCHED) ) { return GetScheduleOfType( SCHED_SMALL_FLINCH ); } else if ( !HasConditions(bits_COND_SEE_ENEMY) ) { // we can't see the enemy if ( !HasConditions(bits_COND_ENEMY_OCCLUDED) ) { // enemy is unseen, but not occluded! // turn to face enemy return GetScheduleOfType( SCHED_COMBAT_FACE ); } else { // chase! return GetScheduleOfType( SCHED_CHASE_ENEMY ); } } else { // we can see the enemy if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) { return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); } if ( HasConditions(bits_COND_CAN_RANGE_ATTACK2) ) { return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); } if ( HasConditions(bits_COND_CAN_MELEE_ATTACK1) ) { return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); } if ( HasConditions(bits_COND_CAN_MELEE_ATTACK2) ) { return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); } if ( !HasConditions(bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1) ) { // if we can see enemy but can't use either attack type, we must need to get closer to enemy return GetScheduleOfType( SCHED_CHASE_ENEMY ); } else if ( !FacingIdeal() ) { //turn return GetScheduleOfType( SCHED_COMBAT_FACE ); } else { ALERT ( at_aiconsole, "No suitable combat schedule!\n" ); } } break; } case MONSTERSTATE_DEAD: { return GetScheduleOfType( SCHED_DIE ); break; } case MONSTERSTATE_SCRIPT: { ASSERT( m_pCine != NULL ); if ( !m_pCine ) { ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); // ALERT( at_console, "Script failed for %s\n", STRING(pev->classname) ); CineCleanup(); return GetScheduleOfType( SCHED_IDLE_STAND ); } return GetScheduleOfType( SCHED_AISCRIPT ); } default: { ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); break; } } return &slError[ 0 ]; }
//========================================================= // Look - Base class monster function to find enemies or // food by sight. iDistance is distance ( in units ) that the // monster can see. // // Sets the sight bits of the m_afConditions mask to indicate // which types of entities were sighted. // Function also sets the Looker's m_pLink // to the head of a link list that contains all visible ents. // (linked via each ent's m_pLink field) // //========================================================= void CBaseMonster :: Look ( int iDistance ) { int iSighted = 0; // DON'T let visibility information from last frame sit around! ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); m_pLink = NULL; CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with CBaseEntity *pList[100]; Vector delta = Vector( iDistance, iDistance, iDistance ); // Find only monsters/clients in box, NOT limited to PVS int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); for ( int i = 0; i < count; i++ ) { pSightEnt = pList[i]; if ( pSightEnt != this && pSightEnt->pev->health > 0 ) { // the looker will want to consider this entity // don't check anything else about an entity that can't be seen, or an entity that you don't care about. if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) { if ( pSightEnt->IsPlayer() ) { // if we see a client, remember that (mostly for scripted AI) iSighted |= bits_COND_SEE_CLIENT; } pSightEnt->m_pLink = m_pLink; m_pLink = pSightEnt; if ( pSightEnt == m_hEnemy ) { // we know this ent is visible, so if it also happens to be our enemy, store that now. iSighted |= bits_COND_SEE_ENEMY; } // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when // we see monsters other than the Enemy. switch ( IRelationship ( pSightEnt ) ) { case R_NM: iSighted |= bits_COND_SEE_NEMESIS; break; case R_HT: iSighted |= bits_COND_SEE_HATE; break; case R_DL: iSighted |= bits_COND_SEE_DISLIKE; break; case R_FR: iSighted |= bits_COND_SEE_FEAR; break; case R_AL: break; default: ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); break; } } } } SetConditions( iSighted ); }
void CLeech::SwimThink( void ) { TraceResult tr; float flLeftSide; float flRightSide; float targetSpeed; float targetYaw = 0; CBaseEntity *pTarget; if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) { pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); pev->velocity = g_vecZero; return; } else pev->nextthink = gpGlobals->time + 0.1; targetSpeed = LEECH_SWIM_SPEED; if ( m_waterTime < gpGlobals->time ) RecalculateWaterlevel(); if ( m_stateTime < gpGlobals->time ) SwitchLeechState(); ClearConditions( bits_COND_CAN_MELEE_ATTACK1 ); switch( m_MonsterState ) { case MONSTERSTATE_COMBAT: pTarget = m_hEnemy; if ( !pTarget ) SwitchLeechState(); else { // Chase the enemy's eyes m_height = pTarget->pev->origin.z + pTarget->pev->view_ofs.z - 5; // Clip to viable water area if ( m_height < m_bottom ) m_height = m_bottom; else if ( m_height > m_top ) m_height = m_top; Vector location = pTarget->pev->origin - pev->origin; location.z += (pTarget->pev->view_ofs.z); if ( location.Length() < 40 ) SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); // Turn towards target ent targetYaw = UTIL_VecToYaw( location ); targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( pev->angles.y ) ); if ( targetYaw < (-LEECH_TURN_RATE*0.75) ) targetYaw = (-LEECH_TURN_RATE*0.75); else if ( targetYaw > (LEECH_TURN_RATE*0.75) ) targetYaw = (LEECH_TURN_RATE*0.75); else targetSpeed *= 2; } break; default: if ( m_zTime < gpGlobals->time ) { float newHeight = RANDOM_FLOAT( m_bottom, m_top ); m_height = 0.5 * m_height + 0.5 * newHeight; m_zTime = gpGlobals->time + RANDOM_FLOAT( 1, 4 ); } if ( RANDOM_LONG( 0, 100 ) < 10 ) targetYaw = RANDOM_LONG( -30, 30 ); pTarget = NULL; // oldorigin test if ( (pev->origin - pev->oldorigin).Length() < 1 ) { // If leech didn't move, there must be something blocking it, so try to turn m_sideTime = 0; } break; } m_obstacle = ObstacleDistance( pTarget ); pev->oldorigin = pev->origin; if ( m_obstacle < 0.1 ) m_obstacle = 0.1; // is the way ahead clear? if ( m_obstacle == 1.0 ) { // if the leech is turning, stop the trend. if ( m_flTurning != 0 ) { m_flTurning = 0; } m_fPathBlocked = FALSE; pev->speed = UTIL_Approach( targetSpeed, pev->speed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME ); pev->velocity = gpGlobals->v_forward * pev->speed; } else { m_obstacle = 1.0 / m_obstacle; // IF we get this far in the function, the leader's path is blocked! m_fPathBlocked = TRUE; if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid { Vector vecTest; // measure clearance on left and right to pick the best dir to turn vecTest = pev->origin + (gpGlobals->v_right * LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); flRightSide = tr.flFraction; vecTest = pev->origin + (gpGlobals->v_right * -LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); flLeftSide = tr.flFraction; // turn left, right or random depending on clearance ratio float delta = (flRightSide - flLeftSide); if ( delta > 0.1 || (delta > -0.1 && RANDOM_LONG(0,100)<50) ) m_flTurning = -LEECH_TURN_RATE; else m_flTurning = LEECH_TURN_RATE; } pev->speed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), pev->speed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle ); pev->velocity = gpGlobals->v_forward * pev->speed; } pev->ideal_yaw = m_flTurning + targetYaw; UpdateMotion(); }
//========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. // // Returns number of events handled, 0 if none. //========================================================= void CFriend :: HandleAnimEvent( MonsterEvent_t *pEvent ) { Vector vecGunPos; Vector vecGunAngles; GetAttachment( 0, vecGunPos, vecGunAngles ); switch( pEvent->event ) { case HGRUNT_AE_BURST1: BarneyFirePistol(); break; //sys case HGRUNT_AE_BURST2: case HGRUNT_AE_BURST3: BarneyFirePistol(); break; /* case HGRUNT_AE_KICK: { CBaseEntity *pHurt = Kick(); if ( pHurt ) { // SOUND HERE! UTIL_MakeVectors( pev->angles ); pHurt->pev->punchangle.x = 15; pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); } } break; */ case HGRUNT_AE_DROP_GUN: { //SP: si tiene el flag de "no tirar arma" cerrar con break y no ejecutar //mas codigo if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC Vector vecGunPos; Vector vecGunAngles; GetAttachment( 0, vecGunPos, vecGunAngles ); SetBodygroup( GUN_GROUP, GUN_NONE ); CBaseEntity *pItem; // now spawn a gun. //if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) if (pev->frags) { pItem = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); pItem->pev->spawnflags |= SF_NORESPAWN; // No respawn } else { pItem = DropItem( "weapon_m16", vecGunPos, vecGunAngles ); pItem->pev->spawnflags |= SF_NORESPAWN; // No respawn } //if -> crash, remove the following // pItem->pev->avelocity = Vector ( RANDOM_FLOAT( -222, 222 ), RANDOM_FLOAT( -222, 222 ),RANDOM_FLOAT( -222, 222 ) ); } break; case HGRUNT_AE_RELOAD: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); m_cAmmoLoaded = m_cClipSize; ClearConditions(bits_COND_NO_AMMO_LOADED); // DropItem( "item_clip_rifle", vecGunPos, vecGunAngles );//test break; case FBARNEY_AE_DRAW: if (pev->frags)//shotgun SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); else SetBodygroup( GUN_GROUP, GUN_MP5 ); m_fGunDrawn = TRUE; break; case HGRUNT_AE_GREN_DROP: { UTIL_MakeVectors( pev->angles ); CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); } break; case HGRUNT_AE_GREN_TOSS: { UTIL_MakeVectors( pev->angles ); // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 ); CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); m_fThrowGrenade = FALSE; m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. } break; default: CTalkMonster::HandleAnimEvent( pEvent ); } }