RunAI()
method which is kinda like a "core loop" had several responsibilities involving senses, enemy acquisition and other things.RunAI()
method, the first thing we can see is that idle sounds (calling IdleSound()
) are performed on a 1% chance (RANDOM_LONG( 0, 99 ) == 0
) if the monster is not gagged (SF_MONSTER_GAG
spawnflag set by level designers) and is idling (m_MonsterState == MONSTERSTATE_IDLE
) or in an alert state (m_MonsterState == MONSTERSTATE_ALERT
).
RunAI()
calls the Look( m_flDistLook )
method. m_flDistLook
is a member variable responsible to define how far the monster can look, it has a default value of 2048 units which is set in the MonsterInit()
method. If you want to override this value, it's best to set it after the call to said method when the monster is spawning (unless the monster does not call MonsterInit()
at all like the leech for example).bits_COND_SEE_ENEMY
, bits_COND_SEE_NEMESIS
and so on). If the monster has the "prisoner" spawnflag (SF_MONSTER_PRISONER
), then the "looking" part is considered to be done and we move on to listening (and smelling) right away.FL_CLIENT
or FL_MONSTER
flag) in a box around the monster the size of m_flDistLook
thanks to CBaseEntity **UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask )
. Likely for performance reasons, the limit of "collected" entities in the box is 100.pSightEnt != this
).R_NO
).bool FInViewCone( pEntity )
returns true).FL_NOTARGET
).bool FVisible( pEntity )
returns true).bool IsPlayer()
returns true) and in the monster's view cone. This is to handle the "wait until seen" spawnflag (SF_MONSTER_WAIT_TILL_SEEN
) that level designers can set on monsters. If the monster did not saw the player that frame, we move to the next client/monster in the list. Otherwise, the spawnflag bit is cleared and the bits_COND_SEE_CLIENT
is set (mostly used by scripting entities like scripted_sequence).m_pLink
member variable (linked list approach), this will be used in enemy acquisition later.m_hEnemy
). If the answer is yes, the bits_COND_SEE_ENEMY
condition flag is set.bits_COND_SEE_FEAR
condition flag when seeing human grunts.
That method (and eventually bool FIsVisible( pEntity )
) would be a nice spot for those who would like to implement "stealth" in their mod. Especially when Valve left a convenient int Illumination()
method to query how much an entity is in the light or darkness.
Listen()
method, we can see that listening (and by extension smelling) is done by resetting the condition flags about it (bits_COND_HEAR_SOUND
, bits_COND_SMELL
and bits_COND_SMELL_FOOD
).iMySounds
retrieve what the monster can hear and smell about (the result of calling int ISoundMask()
). If the monster has a schedule (m_pSchedule != nullptr
), then the sound mask(s) of said schedule are added.m_afSoundTypes
so other parts of the code can use it.virtual Vector EarPosition()
). By default, this is a copy/paste of the monster's eyes position code. None of the Half-Life monsters does override the position of ears.bool GetEnemy()
. First, it check if any condition flag involving seeing a nemesis, hated or disliked monster is set.CBaseEntity *BestVisibleEnemy()
) is determined.
If the monster is running a schedule, had an actual enemy and the best visible enemy is not the actual one (pNewEnemy != m_hEnemy && pNewEnemy != nullptr
), then two things could happen:
bits_COND_NEW_ENEMY
interruption mask, then the best visible enemy becomes a priority (a call to PushEnemy( m_hEnemy, m_vecEnemyLKP )
is made, the bits_COND_NEW_ENEMY
condition flag is set and the actual enemy becomes the new one).PushEnemy( m_hEnemy, m_vecEnemyLKP )
).m_hEnemy
) and a list of "old enemies" up to MAX_OLD_ENEMIES
(4) that works like a "stack" (hence why the PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos )
and bool PopEnemy()
methods).bool CheckEnemy()
to happen, the result of the first part must be true.bits_COND_ENEMY_OCCLUDED
) based on it's visibility.bool IsAlive()
). Assuming the enemy is still alive, information about the enemy's position and distance are gathered.bits_COND_ENEMY_FACING_ME
condition flag.m_flDistTooFar
, then the bits_COND_ENEMY_TOOFAR
condition flag is set. Otherwise, it's cleared.
If the monster is allowed to check attacks (a call of bool FCanCheckAttacks()
which by default check the condition flags "can see the enemy" and "enemy not too far"), then it will check the attacks (call to CheckAttacks()
). Checking attacks basically involves checking if the monster if capable of doing a kind of attack (primary/secondary melee/ranged), call the appropriate check method which is usually overriden by monsters and if the result is true, the appropriate condition flag is set. Example: if bool CheckRangeAttack1( float flDot, float flDist )
returns true then the bits_COND_CAN_RANGE_ATTACK1
condition flag is set (assuming m_afCapability & bits_CAP_RANGE_ATTACK1
is true).m_movementGoal == MOVEGOAL_ENEMY
), has a route of type "goal" or "enemy" (bits_MF_IS_GOAL | bits_MF_TO_ENEMY
) but the enemy's last known position's (m_vecEnemyLKP
) distance is higher than 80 units to said route, then the entire route is refreshed (FRefreshRoute()
). Don't worry if you don't understand this part right away, navigation will be covered in another page when we'll get there.
CheckAmmo()
is made. Only the human grunts (and our Armored Man from the basic/squad monster tutorial) override it so they can check if m_cAmmoLoaded
is equals or less than 0 and set the condition flag that they need to reload if that's the case. You should know what happens next by now.FCheckAITrigger()
is made, this method is very interesting for level designers because it is responsible for handling the "trigger condition" and "trigger target" properties they set on the monsters. If you don't know what "trigger conditions" are, those are basically "when the monster is dead", "when the monster has half of his health", "when the monster sees the player" and "trigger target" is the name of the entity to call/fire when said "trigger condition" is true.PrescheduleThink()
is made, this is an optional spot to do things before the entire schedules/tasks management happen.MaintainSchedule()
does. If not, you should consider (re-)reading the page about schedules and tasks.
You must log in to post a comment. You can login or register a new account.