//----------------------------------------------------------------------------- // Purpose: Called when we have no scripted target. Looks for new enemies to track. //----------------------------------------------------------------------------- CBaseEntity *CNPC_CombineCamera::MaintainEnemy() { if (HasSpawnFlags(SF_COMBINE_CAMERA_IGNOREENEMIES)) return NULL; GetSenses()->Look(m_nOuterRadius); CBaseEntity *pEnemy = BestEnemy(); if (pEnemy) { // See if our best enemy is too far away to care about. Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin(); float flDist = vecDelta.Length(); if (flDist < m_nOuterRadius) { if (FInViewCone(pEnemy)) { // dvs: HACK: for checking multiple view cones float flSaveFieldOfView = m_flFieldOfView; m_flFieldOfView = CAMERA_FOV_NARROW; // Is the target visible? bool bVisible = FVisible(pEnemy); m_flFieldOfView = flSaveFieldOfView; if ( bVisible ) return pEnemy; } } } return NULL; }
//========================================================= // IdleHello // Try to greet player first time he's seen //========================================================= int CAI_PlayerAlly::FIdleHello( void ) { if (!IsOkToSpeak()) return false; // if this is first time scientist has seen player, greet him if (!GetExpresser()->SpokeConcept(TLK_HELLO)) { // get a player CBaseEntity *pPlayer = FindNearestFriend(true); if (pPlayer) { if (FInViewCone(pPlayer) && FVisible(pPlayer)) { SetSpeechTarget( pPlayer ); Speak( TLK_HELLO ); return true; } } } return false; }
//========================================================= // IdleHello // Try to greet player first time he's seen //========================================================= bool CTalkMonster::FIdleHello() { if (!FOkToSpeak()) return false; // if this is first time scientist has seen player, greet him if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) { // get a player CBaseEntity *pPlayer = FindNearestFriend(true); if (pPlayer) { if (FInViewCone(pPlayer) && FVisible(pPlayer)) { m_hTalkTarget = pPlayer; if ( GetSpawnFlags().Any( SF_MONSTER_PREDISASTER ) ) PlaySentence( m_szGrp[TLK_PHELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); else PlaySentence( m_szGrp[TLK_HELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); SetBits(m_bitsSaid, bit_saidHelloPlayer); return true; } } } return false; }
// here bot updates important info that is used multiple times along the thinking process void CSDKBot::InfoGathering() { if (!GetEnemy()) { m_flBotToEnemyDist = 9999; m_flHeightDifToEnemy = 0; m_bEnemyOnSights = false; m_flDistTraveled += fabs(GetLocalVelocity().Length()); // this is used for stuck checking, return; } m_flBotToEnemyDist = (GetLocalOrigin() - GetEnemy()->GetLocalOrigin()).Length(); trace_t tr; UTIL_TraceHull( EyePosition(), GetEnemy()->EyePosition() - Vector(0,0,20), -BotTestHull, BotTestHull, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if( tr.m_pEnt == GetEnemy() ) // vision line between both m_bEnemyOnSights = true; else m_bEnemyOnSights = false; m_bInRangeToAttack = (m_flBotToEnemyDist < m_flMinRangeAttack) && FInViewCone( GetEnemy() ); m_flDistTraveled += fabs(GetLocalVelocity().Length()); // this is used for stuck checking, m_flHeightDifToEnemy = GetLocalOrigin().z - GetEnemy()->GetLocalOrigin().z; }
// This function takes a lot of CPU, so make sure it's not called often! Don't call this function directly, use UpdateEnemy instead whenever possible. CBaseEntity* AvHTurret::FindBestEnemy() { PROFILE_START() CBaseEntity* theEntityList[100]; int theMaxRange = this->GetXYRange(); Vector delta = Vector(theMaxRange, theMaxRange, theMaxRange); CBaseEntity* theCurrentEntity = NULL; CBaseEntity* theBestPlayer = NULL; CBaseEntity* theBestStructure = NULL; float theCurrentEntityRange = 100000; // Find only monsters/clients in box, NOT limited to PVS int theCount = UTIL_EntitiesInBox(theEntityList, 100, this->pev->origin - delta, this->pev->origin + delta, FL_CLIENT | FL_MONSTER); for(int i = 0; i < theCount; i++ ) { theCurrentEntity = theEntityList[i]; if((theCurrentEntity != this) && theCurrentEntity->IsAlive()) { // 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(this->IRelationship(theCurrentEntity ) != R_NO && FInViewCone(theCurrentEntity) && !FBitSet(theCurrentEntity->pev->flags, FL_NOTARGET)) { AvHPlayer* thePlayer = dynamic_cast<AvHPlayer*>(theCurrentEntity); if(!thePlayer || thePlayer->GetCanBeAffectedByEnemies()) { if(this->GetIsValidTarget(theCurrentEntity)) { // Find nearest enemy float theRangeToTarget = VectorDistance2D(this->pev->origin, theCurrentEntity->pev->origin); if(theRangeToTarget < theCurrentEntityRange) { // FVisible is expensive, so defer until necessary if(!this->GetRequiresLOS() || FVisible(theCurrentEntity)) { theCurrentEntityRange = theRangeToTarget; if ( thePlayer ) { theBestPlayer = theCurrentEntity; } else { theBestStructure = theCurrentEntity; } } } } } } } } PROFILE_END(kAvHTurretFindBestEnemy); return (theBestPlayer != NULL ) ? theBestPlayer : theBestStructure; }
//----------------------------------------------------------------------------- // Purpose: Enemies are only valid if they're inside our radius //----------------------------------------------------------------------------- bool CNPC_CombineCamera::IsValidEnemy( CBaseEntity *pEnemy ) { Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin(); float flDist = vecDelta.Length(); if ( (flDist > m_nOuterRadius) || !FInViewCone(pEnemy) ) return false; return BaseClass::IsValidEnemy( pEnemy ); }
//--------------------------------------------------------- //--------------------------------------------------------- bool CNPC_GroundTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) { if ( BaseClass::FVisible( pEntity, traceMask, ppBlocker ) ) return true; if ( ( pEntity->GetAbsOrigin().AsVector2D() - GetAbsOrigin().AsVector2D() ).LengthSqr() < Square(10*12) && FInViewCone( pEntity->GetAbsOrigin() ) && BaseClass::FVisible( pEntity->GetAbsOrigin() + Vector( 0, 0, 1 ), traceMask, ppBlocker ) ) return true; return false; }
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); }
//----------------------------------------------------------------------------- // Purpose: Try to greet player first time he's seen // Output : int //----------------------------------------------------------------------------- int CNPCSimpleTalker::FIdleHello( void ) { // Filter might be preventing us from ever greeting the player if ( !CanSayHello() ) return false; // get a player CBaseEntity *pPlayer = FindNearestFriend(true); if (pPlayer) { if (FInViewCone(pPlayer) && FVisible(pPlayer)) { SayHelloToPlayer( pPlayer ); return true; } } return false; }
//----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CNPC_SecobModportal1::CanBeAnEnemyOf( CBaseEntity *pEnemy ) { static const float flFullFov = cos( DEG2RAD(360) / 2.0 ); if ( fabsf( m_flFieldOfView - flFullFov ) > .01 ) { if ( !FInViewCone( pEnemy ) ) { return false; } } if ( m_flMinDistValidEnemy > 0 ) { float distSq = ( GetAbsOrigin().AsVector2D() - pEnemy->GetAbsOrigin().AsVector2D() ).LengthSqr(); if ( distSq < Square( m_flMinDistValidEnemy ) ) { return false; } } return BaseClass::CanBeAnEnemyOf( pEnemy ); }
//----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CNPC_Combine_Cannon::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC ) { Disposition_t disp = IRelationType(pEntity); if ( disp != D_HT ) { // Don't bother with anything I wouldn't shoot. return false; } if ( !FInViewCone(pEntity) ) { // Yes, this does call FInViewCone twice a frame for all entities checked for // visibility, but doing this allows us to cut out a bunch of traces that would // be done by VerifyShot for entities that aren't even in our viewcone. return false; } if ( VerifyShot( pEntity ) ) return BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC ); return false; }
//========================================================= // follower boids execute this code when flocking //========================================================= void CFlockingFlyer :: FlockFollowerThink( void ) { TraceResult tr; Vector vecDist; Vector vecDir; Vector vecDirToLeader; float flDistToLeader; pev->nextthink = gpGlobals->time + 0.1; if ( IsLeader() || !InSquad() ) { // the leader has been killed and this flyer suddenly finds himself the leader. SetThink ( &CFlockingFlyer::FlockLeaderThink ); return; } vecDirToLeader = ( m_pSquadLeader->pev->origin - pev->origin ); flDistToLeader = vecDirToLeader.Length(); // match heading with leader pev->angles = m_pSquadLeader->pev->angles; // // We can see the leader, so try to catch up to it // if ( FInViewCone ( m_pSquadLeader ) ) { // if we're too far away, speed up if ( flDistToLeader > AFLOCK_TOO_FAR ) { m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 1.5; } // if we're too close, slow down else if ( flDistToLeader < AFLOCK_TOO_CLOSE ) { m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; } } else { // wait up! the leader isn't out in front, so we slow down to let him pass m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; } SpreadFlock2(); pev->speed = pev->velocity.Length(); pev->velocity = pev->velocity.Normalize(); // if we are too far from leader, average a vector towards it into our current velocity if ( flDistToLeader > AFLOCK_TOO_FAR ) { vecDirToLeader = vecDirToLeader.Normalize(); pev->velocity = (pev->velocity + vecDirToLeader) * 0.5; } // clamp speeds and handle acceleration if ( m_flGoalSpeed > AFLOCK_FLY_SPEED * 2 ) { m_flGoalSpeed = AFLOCK_FLY_SPEED * 2; } if ( pev->speed < m_flGoalSpeed ) { pev->speed += AFLOCK_ACCELERATE; } else if ( pev->speed > m_flGoalSpeed ) { pev->speed -= AFLOCK_ACCELERATE; } pev->velocity = pev->velocity * pev->speed; BoidAdvanceFrame( ); }
//========================================================= // 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 CBaseTurret::ActiveThink(void) { int fAttack = 0; Vector vecDirToEnemy; SetNextThink( gpGlobals->curtime + 0.1f ); StudioFrameAdvance( ); if ((!m_iOn) || (GetEnemy() == NULL)) { SetEnemy( NULL ); m_flLastSight = gpGlobals->curtime + m_flMaxWait; SetThink(SearchThink); return; } // if it's dead, look for something new if ( !GetEnemy()->IsAlive() ) { if (!m_flLastSight) { m_flLastSight = gpGlobals->curtime + 0.5; // continue-shooting timeout } else { if (gpGlobals->curtime > m_flLastSight) { SetEnemy( NULL ); m_flLastSight = gpGlobals->curtime + m_flMaxWait; SetThink(SearchThink); return; } } } Vector vecMid = EyePosition( ); Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid); // g_pEffects->Sparks( vecMid ); // g_pEffects->Sparks( vecMidEnemy ); // Look for our current enemy //int fEnemyVisible = FBoxVisible( this, GetEnemy(), vecMidEnemy ); int fEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() ); vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy // NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.1 ); float flDistToEnemy = vecDirToEnemy.Length(); QAngle vecAnglesToEnemy; VectorNormalize( vecDirToEnemy ); VectorAngles( vecDirToEnemy, vecAnglesToEnemy ); // Current enmey is not visible. if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) { // DevMsg( "lost you\n" ); if (!m_flLastSight) { m_flLastSight = gpGlobals->curtime + 0.5; } else { // Should we look for a new target? if (gpGlobals->curtime > m_flLastSight) { ClearEnemyMemory(); SetEnemy( NULL ); m_flLastSight = gpGlobals->curtime + m_flMaxWait; SetThink(SearchThink); return; } } fEnemyVisible = 0; } else { m_vecLastSight = vecMidEnemy; } Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; VectorNormalize( vecLOS ); Vector vecMuzzle, vecMuzzleDir; QAngle vecMuzzleAng; GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); AngleVectors( vecMuzzleAng, &vecMuzzleDir ); // Is the Gun looking at the target if (DotProduct(vecLOS, vecMuzzleDir) <= 0.9848) // 10 degree slop { fAttack = FALSE; } else { fAttack = TRUE; } // fire the gun if (fAttack || m_fBeserk) { m_Activity = ACT_RESET; SetActivity( (Activity)ACT_TURRET_FIRE ); Shoot(vecMuzzle, vecMuzzleDir ); } else { SetActivity( (Activity)ACT_TURRET_OPEN_IDLE ); } //move the gun if (m_fBeserk) { // DevMsg( "berserk" ); if (random->RandomInt(0,9) == 0) { m_vecGoalAngles.y = random->RandomFloat(-180,180); m_vecGoalAngles.x = random->RandomFloat(-90,90); OnTakeDamage( CTakeDamageInfo( this, this, 1, DMG_GENERIC ) ); // don't beserk forever return; } } else if (fEnemyVisible) { // DevMsg( "->[%.2f]\n", vec.x); m_vecGoalAngles.y = vecAnglesToEnemy.y; m_vecGoalAngles.x = vecAnglesToEnemy.x; } MoveTurret(); }
bool CBaseBot::FindEnemy() { // check if the health is decreased bool fHealthDecreased = m_iPrevHealth > GetHealth(); m_iPrevHealth = GetHealth(); // store away the current health value float cur_dist; if (m_pEnemy && (!m_pEnemy->IsValid() || !m_pEnemy->IsAlive())) m_pEnemy = NULL; // null out the enemy pointer as it's no longer valid Vector vecHisPos; unsigned char cHit; // see if we can still see the current enemy... if (m_pEnemy) { if (FBoxVisible(m_pEnemy, &vecHisPos, &cHit)) { m_vecEnemy = vecHisPos; m_ucVisibility = cHit; } else { m_pEnemy = NULL; // we can no longer see this enemy } } // if we already have an enemy... if (m_pEnemy) { // don't discard important enemies (bomb/flag/hostage carrier, VIP, etc) if (g_pServer->ClientIsImportant(EnemyClient())) return false; // calculate the distance to the enemy cur_dist = (m_pEnemy->GetOrigin() - GetOrigin()).Length(); } else { cur_dist = FLT_MAX; // just some crazy value } // loop through all the clients... for (int i = 0; i < g_pServer->GetMaxClients(); i++) { if (i == entindex() - 1 || (m_pEnemy && i == m_pEnemy->entindex() - 1)) continue; // skip myself and the current enemy CClient *pClient = g_pServer->m_rgpClients[i]; if (!pClient || !pClient->IsValid() || !pClient->IsAlive()) continue; float dist = (pClient->GetOrigin() - GetOrigin()).Length(); // if this enemy is further away than the current one... if (dist > cur_dist && !g_pServer->ClientIsImportant(pClient)) continue; // skip it if (dist > 900 + 4000 * ((GetDifficulty() - 1) / 4.0)) continue; // enemy is too far if (g_pServer->IsTeamplay() && GetTeam() == g_pServer->GetTeam(pClient)) continue; // skip our teammates float fov; // if the bot's health decreased or the enemy is shooting if (!m_pEnemy && (fHealthDecreased || pClient->IsShooting())) fov = 360; else fov = GetFov() * 2 - (GetFov() - (dist > GetFov() * 9 ? GetFov() * 9 : dist) / 9); // check if enemy is in the view cone if (!FInViewCone(pClient, fov)) continue; // enemy isn't in bot's view cone // check if enemy is visible if (!FBoxVisible(pClient, &vecHisPos, &cHit)) { continue; // skip this enemy } // if the enemy is quite far away, not shooting and the bot is not damaged if (!m_pEnemy && dist > 200 && !fHealthDecreased && !pClient->IsShooting()) { // if the bot isn't in the fov of the enemy and the bot doesn't really want to fight if (!pClient->FInViewCone(this, 120) /*&& BotWantsToRetreat()*/) continue; // skip this enemy } m_pEnemy = pClient; // found a new enemy m_vecEnemy = vecHisPos; m_ucVisibility = cHit; DebugMsg(DEBUG_BOTCOMBAT, "Found new enemy: %s", m_pEnemy->GetNetName()); return true; } return false; // no new enemy is found }
//========================================================= // GetSchedule //========================================================= int CNPC_Bullsquid::SelectSchedule( void ) { switch ( m_NPCState ) { case NPC_STATE_ALERT: { if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) { return SCHED_SQUID_HURTHOP; } if ( HasCondition( COND_SQUID_SMELL_FOOD ) ) { CSound *pSound; pSound = GetBestScent(); if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) ) { // scent is behind or occluded return SCHED_SQUID_SNIFF_AND_EAT; } // food is right out in the open. Just go get it. return SCHED_SQUID_EAT; } if ( HasCondition( COND_SMELL ) ) { // there's something stinky. CSound *pSound; pSound = GetBestScent(); if ( pSound ) return SCHED_SQUID_WALLOW; } break; } 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_NEW_ENEMY ) ) { if ( m_fCanThreatDisplay && IRelationType( GetEnemy() ) == D_HT && FClassnameIs( GetEnemy(), "monster_headcrab" ) ) { // this means squid sees a headcrab! m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime. return SCHED_SQUID_SEECRAB; } else { return SCHED_WAKE_ANGRY; } } if ( HasCondition( COND_SQUID_SMELL_FOOD ) ) { CSound *pSound; pSound = GetBestScent(); if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) ) { // scent is behind or occluded return SCHED_SQUID_SNIFF_AND_EAT; } // food is right out in the open. Just go get it. return SCHED_SQUID_EAT; } if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) { return SCHED_RANGE_ATTACK1; } if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) { return SCHED_MELEE_ATTACK1; } if ( HasCondition( COND_CAN_MELEE_ATTACK2 ) ) { return SCHED_MELEE_ATTACK2; } return SCHED_CHASE_ENEMY; break; } } return BaseClass::SelectSchedule(); }
//========================================================= // GetSchedule //========================================================= Schedule_t *CBullsquid :: GetSchedule( void ) { switch ( m_MonsterState ) { case MONSTERSTATE_ALERT: { if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) { return GetScheduleOfType ( SCHED_SQUID_HURTHOP ); } if ( HasConditions(bits_COND_SMELL_FOOD) ) { CSound *pSound; pSound = PBestScent(); if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) { // scent is behind or occluded return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); } // food is right out in the open. Just go get it. return GetScheduleOfType( SCHED_SQUID_EAT ); } if ( HasConditions(bits_COND_SMELL) ) { // there's something stinky. CSound *pSound; pSound = PBestScent(); if ( pSound ) return GetScheduleOfType( SCHED_SQUID_WALLOW); } break; } case MONSTERSTATE_COMBAT: { // dead enemy if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return CBaseMonster :: GetSchedule(); } if ( HasConditions(bits_COND_NEW_ENEMY) ) { if ( m_fCanThreatDisplay && IRelationship( m_hEnemy ) == R_HT ) { // this means squid sees a headcrab! m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime. return GetScheduleOfType ( SCHED_SQUID_SEECRAB ); } else { return GetScheduleOfType ( SCHED_WAKE_ANGRY ); } } if ( HasConditions(bits_COND_SMELL_FOOD) ) { CSound *pSound; pSound = PBestScent(); if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) { // scent is behind or occluded return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); } // food is right out in the open. Just go get it. return GetScheduleOfType( SCHED_SQUID_EAT ); } if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); } if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); } if ( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) ) { return GetScheduleOfType ( SCHED_MELEE_ATTACK2 ); } return GetScheduleOfType ( SCHED_CHASE_ENEMY ); break; } } return CBaseMonster :: GetSchedule(); }
//----------------------------------------------------------------------------- // Purpose: Allows the turret to fire on targets if they're visible //----------------------------------------------------------------------------- void CNPC_CeilingTurret::ActiveThink( void ) { //Allow descended classes a chance to do something before the think function if ( PreThink( TURRET_ACTIVE ) ) return; //Update our think time SetNextThink( gpGlobals->curtime + 0.1f ); //If we've become inactive, go back to searching if ( ( m_bActive == false ) || ( GetEnemy() == NULL ) ) { SetEnemy( NULL ); SetLastSightTime(); SetThink( &CNPC_CeilingTurret::SearchThink ); m_vecGoalAngles = GetAbsAngles(); return; } //Get our shot positions Vector vecMid = EyePosition(); Vector vecMidEnemy = GetEnemy()->GetAbsOrigin(); //Store off our last seen location UpdateEnemyMemory( GetEnemy(), vecMidEnemy ); //Look for our current enemy bool bEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() ) && GetEnemy()->IsAlive(); //Calculate dir and dist to enemy Vector vecDirToEnemy = vecMidEnemy - vecMid; float flDistToEnemy = VectorNormalize( vecDirToEnemy ); //We want to look at the enemy's eyes so we don't jitter Vector vecDirToEnemyEyes = GetEnemy()->WorldSpaceCenter() - vecMid; VectorNormalize( vecDirToEnemyEyes ); QAngle vecAnglesToEnemy; VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy ); //Draw debug info if ( g_debug_turret_ceiling.GetBool() ) { NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Cross3D( GetEnemy()->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Line( vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05 ); NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f ); } //Current enemy is not visible if ( ( bEnemyVisible == false ) || ( flDistToEnemy > CEILING_TURRET_RANGE )) { if ( m_flLastSight ) { m_flLastSight = gpGlobals->curtime + 0.5f; } else if ( gpGlobals->curtime > m_flLastSight ) { // Should we look for a new target? ClearEnemyMemory(); SetEnemy( NULL ); SetLastSightTime(); SetThink( &CNPC_CeilingTurret::SearchThink ); m_vecGoalAngles = GetAbsAngles(); SpinDown(); return; } bEnemyVisible = false; } Vector vecMuzzle, vecMuzzleDir; QAngle vecMuzzleAng; GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); AngleVectors( vecMuzzleAng, &vecMuzzleDir ); if ( m_flShotTime < gpGlobals->curtime ) { //Fire the gun if ( DotProduct( vecDirToEnemy, vecMuzzleDir ) >= 0.9848 ) // 10 degree slop { if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) { SetActivity( (Activity) ACT_CEILING_TURRET_DRYFIRE ); } else { SetActivity( (Activity) ACT_CEILING_TURRET_FIRE ); } //Fire the weapon Shoot( vecMuzzle, vecMuzzleDir ); } } else { SetActivity( (Activity) ACT_CEILING_TURRET_OPEN_IDLE ); } //If we can see our enemy, face it if ( bEnemyVisible ) { m_vecGoalAngles.y = vecAnglesToEnemy.y; m_vecGoalAngles.x = vecAnglesToEnemy.x; } //Turn to face UpdateFacing(); }
//----------------------------------------------------------------------------- // Purpose: Target doesn't exist or has eluded us, so search for one //----------------------------------------------------------------------------- void CNPC_Portal_FloorTurret::SearchThink( void ) { //Allow descended classes a chance to do something before the think function if ( PreThink( TURRET_SEARCHING ) ) return; SetNextThink( gpGlobals->curtime + 0.05f ); SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE ); //If our enemy has died, pick a new enemy if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) ) { SetEnemy( NULL ); } //Acquire the target if ( GetEnemy() == NULL ) { HackFindEnemy(); } LaserOn(); CBaseEntity *pEnemy = GetEnemy(); //If we've found a target, spin up the barrel and start to attack if ( pEnemy != NULL ) { //Get our shot positions Vector vecMid = EyePosition(); Vector vecMidEnemy = pEnemy->BodyTarget( vecMid ); //Look for our current enemy bool bEnemyInFOV = FInViewCone( pEnemy ); bool bEnemyVisible = FVisible( pEnemy ); //Calculate dir and dist to enemy Vector vecDirToEnemy = vecMidEnemy - vecMid; m_flDistToEnemy = VectorNormalize( vecDirToEnemy ); // If the enemy isn't in the normal fov, check the fov through portals CProp_Portal *pPortal = NULL; pPortal = FInViewConeThroughPortal( pEnemy ); if ( pPortal && FVisibleThroughPortal( pPortal, pEnemy ) ) { // Translate our target across the portal Vector vecMidEnemyTransformed; UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecMidEnemy, vecMidEnemyTransformed ); //Calculate dir and dist to enemy Vector vecDirToEnemyTransformed = vecMidEnemyTransformed - vecMid; float flDistToEnemyTransformed = VectorNormalize( vecDirToEnemyTransformed ); // If it's not visible through normal means or the enemy is closer through the portal, use the translated info if ( !bEnemyInFOV || !bEnemyVisible || flDistToEnemyTransformed < m_flDistToEnemy ) { bEnemyInFOV = true; bEnemyVisible = true; vecMidEnemy = vecMidEnemyTransformed; vecDirToEnemy = vecDirToEnemyTransformed; m_flDistToEnemy = flDistToEnemyTransformed; } } // Give enemies that are farther away a longer grace period float fDistanceRatio = m_flDistToEnemy / PORTAL_FLOOR_TURRET_RANGE; m_flShotTime = gpGlobals->curtime + fDistanceRatio * fDistanceRatio * PORTAL_FLOOR_TURRET_MAX_SHOT_DELAY; m_flLastSight = 0; SetThink( &CNPC_FloorTurret::ActiveThink ); SetEyeState( TURRET_EYE_SEE_TARGET ); SpinUp(); if ( gpGlobals->curtime > m_flNextActivateSoundTime ) { EmitSound( "NPC_FloorTurret.Activate" ); m_flNextActivateSoundTime = gpGlobals->curtime + 3.0; } return; } //Are we out of time and need to retract? if ( gpGlobals->curtime > m_flLastSight ) { //Before we retrace, make sure that we are spun down. m_flLastSight = 0; SetThink( &CNPC_FloorTurret::Retire ); return; } //Display that we're scanning m_vecGoalAngles.x = GetAbsAngles().x + ( sin( ( m_flLastSight + gpGlobals->curtime * m_fSearchSpeed ) * 1.5f ) * 20.0f ); m_vecGoalAngles.y = GetAbsAngles().y + ( sin( ( m_flLastSight + gpGlobals->curtime * m_fSearchSpeed ) * 2.5f ) * 20.0f ); //Turn and ping UpdateFacing(); Ping(); // Update rope positions for ( int iRope = 0; iRope < PORTAL_FLOOR_TURRET_NUM_ROPES; ++iRope ) { if ( m_hRopes[ iRope ] ) { m_hRopes[ iRope ]->EndpointsChanged(); } } }
void CNPC_Portal_FloorTurret::ActiveThink( void ) { LaserOn(); //Allow descended classes a chance to do something before the think function if ( PreThink( TURRET_ACTIVE ) ) return; HackFindEnemy(); //Update our think time SetNextThink( gpGlobals->curtime + 0.1f ); CBaseEntity *pEnemy = GetEnemy(); //If we've become inactive, go back to searching if ( ( m_bActive == false ) || ( pEnemy == NULL ) ) { SetEnemy( NULL ); m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT; SetThink( &CNPC_FloorTurret::SearchThink ); m_vecGoalAngles = GetAbsAngles(); return; } //Get our shot positions Vector vecMid = EyePosition(); Vector vecMidEnemy = pEnemy->BodyTarget( vecMid ); // Store off our last seen location so we can suppress it later m_vecEnemyLKP = vecMidEnemy; //Look for our current enemy bool bEnemyInFOV = FInViewCone( pEnemy ); bool bEnemyVisible = FVisible( pEnemy ) && pEnemy->IsAlive(); //Calculate dir and dist to enemy Vector vecDirToEnemy = vecMidEnemy - vecMid; m_flDistToEnemy = VectorNormalize( vecDirToEnemy ); // If the enemy isn't in the normal fov, check the fov through portals CProp_Portal *pPortal = NULL; if ( pEnemy->IsAlive() ) { pPortal = FInViewConeThroughPortal( pEnemy ); if ( pPortal && FVisibleThroughPortal( pPortal, pEnemy ) ) { // Translate our target across the portal Vector vecMidEnemyTransformed; UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecMidEnemy, vecMidEnemyTransformed ); //Calculate dir and dist to enemy Vector vecDirToEnemyTransformed = vecMidEnemyTransformed - vecMid; float flDistToEnemyTransformed = VectorNormalize( vecDirToEnemyTransformed ); // If it's not visible through normal means or the enemy is closer through the portal, use the translated info if ( !bEnemyInFOV || !bEnemyVisible || flDistToEnemyTransformed < m_flDistToEnemy ) { bEnemyInFOV = true; bEnemyVisible = true; vecMidEnemy = vecMidEnemyTransformed; vecDirToEnemy = vecDirToEnemyTransformed; m_flDistToEnemy = flDistToEnemyTransformed; } else { pPortal = NULL; } } else { pPortal = NULL; } } //Draw debug info if ( g_debug_turret.GetBool() ) { NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Cross3D( GetEnemy()->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Line( vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05 ); NDebugOverlay::Cross3D( vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Cross3D( vecMidEnemy, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05 ); NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f ); } //See if they're past our FOV of attack if ( bEnemyInFOV == false ) { // Should we look for a new target? ClearEnemyMemory(); SetEnemy( NULL ); if ( m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE ) { // Retire quickly in this case. (The case where we saw the player, but he hid again). m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT; } else { m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT; } SetThink( &CNPC_FloorTurret::SearchThink ); m_vecGoalAngles = GetAbsAngles(); SpinDown(); return; } //Current enemy is not visible if ( ( bEnemyVisible == false ) || ( m_flDistToEnemy > PORTAL_FLOOR_TURRET_RANGE )) { m_flLastSight = gpGlobals->curtime + 2.0f; ClearEnemyMemory(); SetEnemy( NULL ); SetThink( &CNPC_FloorTurret::SuppressThink ); return; } if ( g_debug_turret.GetBool() ) { Vector vecMuzzle, vecMuzzleDir; UpdateMuzzleMatrix(); MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir ); MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle ); // Visualize vertical firing ranges for ( int i = 0; i < 4; i++ ) { QAngle angMaxDownPitch = GetAbsAngles(); switch( i ) { case 0: angMaxDownPitch.x -= 15; break; case 1: angMaxDownPitch.x += 15; break; case 2: angMaxDownPitch.x -= 25; break; case 3: angMaxDownPitch.x += 25; break; default: break; } Vector vecMaxDownPitch; AngleVectors( angMaxDownPitch, &vecMaxDownPitch ); NDebugOverlay::Line( vecMuzzle, vecMuzzle + (vecMaxDownPitch*256), 255, 255, 255, false, 0.1 ); } } if ( m_flShotTime < gpGlobals->curtime ) { Vector vecMuzzle, vecMuzzleDir; UpdateMuzzleMatrix(); MatrixGetColumn( m_muzzleToWorld, 0, vecMuzzleDir ); MatrixGetColumn( m_muzzleToWorld, 3, vecMuzzle ); Vector2D vecDirToEnemy2D = vecDirToEnemy.AsVector2D(); Vector2D vecMuzzleDir2D = vecMuzzleDir.AsVector2D(); bool bCanShoot = true; float minCos3d = DOT_10DEGREE; // 10 degrees slop if ( m_flDistToEnemy < 60.0 ) { vecDirToEnemy2D.NormalizeInPlace(); vecMuzzleDir2D.NormalizeInPlace(); bCanShoot = ( vecDirToEnemy2D.Dot(vecMuzzleDir2D) >= DOT_10DEGREE ); minCos3d = 0.7071; // 45 degrees } //Fire the gun if ( bCanShoot ) // 10 degree slop XY { float dot3d = DotProduct( vecDirToEnemy, vecMuzzleDir ); if( m_bOutOfAmmo ) { DryFire(); } else { if ( dot3d >= minCos3d ) { SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE ); SetActivity( (Activity)( ( m_bShootWithBottomBarrels ) ? ( ACT_FLOOR_TURRET_FIRE2 ) : ( ACT_FLOOR_TURRET_FIRE ) ) ); //Fire the weapon #if !DISABLE_SHOT Shoot( vecMuzzle, vecMuzzleDir, (dot3d < DOT_10DEGREE) ); #endif } } } } else { SetActivity( (Activity) ACT_FLOOR_TURRET_OPEN_IDLE ); } //If we can see our enemy, face it if ( bEnemyVisible ) { //We want to look at the enemy's eyes so we don't jitter Vector vEnemyWorldSpaceCenter = pEnemy->WorldSpaceCenter(); if ( pPortal && pPortal->IsActivedAndLinked() ) { // Translate our target across the portal UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vEnemyWorldSpaceCenter, vEnemyWorldSpaceCenter ); } Vector vecDirToEnemyEyes = vEnemyWorldSpaceCenter - vecMid; VectorNormalize( vecDirToEnemyEyes ); QAngle vecAnglesToEnemy; VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy ); m_vecGoalAngles.y = vecAnglesToEnemy.y; m_vecGoalAngles.x = vecAnglesToEnemy.x; } //Turn to face UpdateFacing(); }
//----------------------------------------------------------------------------- // Purpose: Think while actively tracking a target. //----------------------------------------------------------------------------- void CNPC_CombineCamera::ActiveThink() { // Allow descended classes a chance to do something before the think function if (PreThink(CAMERA_ACTIVE)) return; // No active target, look for suspicious characters. CBaseEntity *pTarget = MaintainEnemy(); if ( !pTarget ) { // Nobody suspicious. Go back to being idle. m_hEnemyTarget = NULL; EmitSound("NPC_CombineCamera.BecomeIdle"); SetAngry(false); SetThink(&CNPC_CombineCamera::SearchThink); SetNextThink( gpGlobals->curtime ); return; } // Examine the target until it reaches our inner radius if ( pTarget != m_hEnemyTarget ) { Vector vecDelta = pTarget->GetAbsOrigin() - GetAbsOrigin(); float flDist = vecDelta.Length(); if ( (flDist < m_nInnerRadius) && FInViewCone(pTarget) ) { m_OnFoundEnemy.Set(pTarget, pTarget, this); // If it's a citizen, it's ok. If it's the player, it's not ok. if ( pTarget->IsPlayer() ) { SetEyeState(CAMERA_EYE_FOUND_TARGET); if (HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY)) { SetAngry(true); } else { EmitSound("NPC_CombineCamera.Active"); } m_OnFoundPlayer.Set(pTarget, pTarget, this); m_hEnemyTarget = pTarget; } else { SetEyeState(CAMERA_EYE_HAPPY); m_flEyeHappyTime = gpGlobals->curtime + 2.0; // Now forget about this target forever AddEntityRelationship( pTarget, D_NU, 99 ); } } else { // If we get angry automatically, we get un-angry automatically if ( HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY) && m_bAngry ) { SetAngry(false); } m_hEnemyTarget = NULL; // We don't quite see this guy, but we sense him. SetEyeState(CAMERA_EYE_SEEKING_TARGET); } } // Update our think time SetNextThink( gpGlobals->curtime + 0.1f ); TrackTarget(pTarget); MaintainEye(); }
bool CEntity::FInViewCone(CEntity *pEntity, float fov) { Vector v_origin = pEntity->GetOrigin(); return FInViewCone(&v_origin, fov); }
bool BotFollowUser( bot_t *pBot ) { bool user_visible; float f_distance; edict_t *pEdict = pBot->pEdict; Vector vecEnd = pBot->pBotUser->v.origin + pBot->pBotUser->v.view_ofs; if (pBot->pEdict->v.waterlevel != 3) // is bot NOT under water? pEdict->v.v_angle.x = 0; // reset pitch to 0 (level horizontally) pEdict->v.v_angle.z = 0; // reset roll to 0 (straight up and down) pEdict->v.angles.x = 0; pEdict->v.angles.y = pEdict->v.v_angle.y; pEdict->v.angles.z = 0; // Stop following when person crouches if (!IsAlive( pBot->pBotUser ) || (pBot->pBotUser->v.flags & FL_DUCKING)) { // the bot's user is dead! pBot->pBotUser = NULL; return FALSE; } user_visible = FInViewCone( &vecEnd, pEdict ) && FVisible( vecEnd, pEdict ); // check if the "user" is still visible or if the user has been visible // in the last 5 seconds (or the player just starting "using" the bot) if (user_visible || (pBot->f_bot_use_time + 5 > gpGlobals->time)) { if (user_visible) pBot->f_bot_use_time = gpGlobals->time; // reset "last visible time" // face the user Vector v_user = pBot->pBotUser->v.origin - pEdict->v.origin; Vector bot_angles = UTIL_VecToAngles( v_user ); pEdict->v.ideal_yaw = bot_angles.y; BotFixIdealYaw(pEdict); f_distance = v_user.Length( ); // how far away is the "user"? if (f_distance > 200) // run if distance to enemy is far pBot->f_move_speed = pBot->f_max_speed; else if (f_distance > 50) // walk if distance is closer pBot->f_move_speed = pBot->f_max_speed / 2; else // don't move if close enough pBot->f_move_speed = 0.0; return TRUE; } else { // person to follow has gone out of sight... pBot->pBotUser = NULL; return FALSE; } }