Monsters Programming - The Concepts of Half-Life's AI Last edited 2 weeks ago2024-10-21 17:20:35 UTC

Half-Life Programming

Now that weapons and entities in general are covered, we may explore another kind of entities: monsters.

Monsters, or NPCs (acronym for "Non-Player Character"), are some of the most difficult entities to program because they have to behave in a certain way to make a game fun. Artificial Intelligence ("AI") is the component responsible for manipulating that behavior.
A squad of human grunts attacking the playerA squad of human grunts attacking the player
We will explore in this page the concepts of Half-Life's AI before we get to create a monster itself. AI and monsters programming always happen in the server project, you will likely never touch the client project. For the sake of clarity, important terminology is in bold.

Classes

Let us do the same thing as we did for the weapons: look at the class declaration of the scientist (CScientist), the human grunt (CHGrunt) and the headcrab (CHeadcrab) and determine the class hierarchy.

The reasons we are looking at those three monsters is that they have distinct traits: one is a "simple" monster, another one is a talkative friend and the last one has the ability to be part and communicate within a squad.

With the knowledge and experience you gained in the previous chapters of this book and/or from looking at the HL SDK by yourself, you should be able to do this on your own.

Need some help? The files you should be looking at are scientist.cpp, hgrunt.cpp and headcrab.cpp. Some IDEs like Visual Studio on Windows provide a "class viewer" to navigate through classes rather than files and this could help you when navigating through the code.

Have you finished? Here is the class hierarchy you should have found:
User posted image
If you found the correct hierarchy, congratulations! If not, do not get discouraged, learn from your mistakes and try again, that's how you gain knowledge and experience.

We can determine that those three monsters use three different bases:
Did you know?
The player (CBasePlayer in player.h) is also a "monster", since its class is based on CBaseMonster.

It may be weird, but if you think about it, players and monsters share some traits like bleeding, dying, moving, attacking, vulnerability to attacks and so on.

This means that a monster does not always involves an AI. For those who are familiar with Unreal Engine, this is a bit like having an APlayerController or AAIController possessing an ACharacter (or anything derived from APawn).

Everything has its beginning

Whenever a monster spawns through its Spawn() method, a call to CBaseMonster::MonsterInit() is made which is responsible for checking if it is allowed to do so using the returned value of bool CGameRules::FAllowMonsters(). The latter returns true or false depending on the game rules implementation used in the CGameRules* g_pGameRules global pointer.

The monster remove itself immediately if that is not the case. Otherwise, it initialize some variables which some of them are described in this page.

You may ask after reading this: "but why Snarks can be used in multiplayer then?". The answer to that is that they aren't really "monsters" but more like "grenades", and this fact is reinforced that CSqueakGrenade is a child of CGrenade and CBaseMonster is not within the hierarchy.

The CBaseMonster::MonsterInit() method make a call to CBaseMonster::StartMonster() which is more interesting because it initialize the monster's "thinking" to CBaseMonster::MonsterThink() creating a loop of calls to CBaseMonster::RunAI() until the monster dies (bool CBaseEntity::IsAlive() returns false).

CBaseMonster::RunAI() has several responsibilities such as: These functions are responsible of toggling on and off condition bits of the monster.

Condition bits

You might have seen in the code of monsters constants like bits_COND_LIGHT_DAMAGE. Those are condition bits. They are used as a way to give information to the AI like "this monster has taken light damage".

They are either disabled or enabled in the monster's m_afConditions attribute. This is done through bitwise operation using the OR operator (|).

In the previous section, we said that several methods such as CBaseMonster::Look() and bool CBaseMonster::GetEnemy() toggle on and/or off these condition bits. CBaseMonster::Look() handles all condition bits that involve vision, such as bits_COND_SEE_FEAR which means "I see an enemy that I'm afraid of".

bool CBaseMonster::GetEnemy() also does it with bits_COND_NEW_ENEMY for "I have a new enemy to deal with", for example. Likewise, CBaseMonster::CheckAmmo() does it with bits_COND_NO_AMMO_LOADED ("my firearm is empty").

Conditions bits are reset when the monster is being "restored" (like loading a saved game) if and only if it doesn't have an enemy.

Here is a list of condition bits common to all monsters, however, keep in mind that a monster may use its own (schedule.h):
Constant Value What it means to the monster
bits_COND_NO_AMMO_LOADED (1 << 0) My firearm is empty.
bits_COND_SEE_HATE (1 << 1) I see an enemy that I hate (more on this later).
bits_COND_SEE_FEAR (1 << 2) I see an enemy that I am afraid of (more on this later).
bits_COND_SEE_DISLIKE (1 << 3) I see an enemy that I dislike (more on this later).
bits_COND_SEE_ENEMY (1 << 4) I see an enemy.
bits_COND_ENEMY_OCCLUDED (1 << 5) Enemy is occluded by the world.
bits_COND_SMELL_FOOD (1 << 6) I smell food.
bits_COND_ENEMY_TOOFAR (1 << 7) My enemy is too far.
bits_COND_LIGHT_DAMAGE (1 << 8) I took light damage, just a flesh wound.
bits_COND_HEAVY_DAMAGE (1 << 9) I took heavy damage, that hurts!
bits_COND_CAN_RANGE_ATTACK1 (1 << 10) I can perform one kind of ranged attack.
bits_COND_CAN_MELEE_ATTACK1 (1 << 11) I can perform one kind of melee attack.
bits_COND_CAN_RANGE_ATTACK2 (1 << 12) I can perform another kind of ranged attack.
bits_COND_CAN_MELEE_ATTACK2 (1 << 13) I can perform another kind of melee attack.
bits_COND_PROVOKED (1 << 15) A friendly (likely the player) provoked me.
bits_COND_NEW_ENEMY (1 << 16) I have a new enemy.
bits_COND_HEAR_SOUND (1 << 17) I hear something interesting.
bits_COND_SMELL (1 << 18) I smell something interesting.
bits_COND_ENEMY_FACING_ME (1 << 19) My enemy is facing me.
bits_COND_ENEMY_DEAD (1 << 20) My enemy is dead.
bits_COND_SEE_CLIENT (1 << 21) I see a client (a bot or a player).
bits_COND_SEE_NEMESIS (1 << 22) I see an enemy which I consider as my nemesis (more on this later).
bits_COND_SPECIAL1 (1 << 28) First special condition specific to my type of monster.
bits_COND_SPECIAL2 (1 << 29) Second special condition specific to my type of monster.
bits_COND_TASK_FAILED (1 << 30) I failed to perform a task (more on this later).
bits_COND_SCHEDULE_DONE (1 << 31) I completed a schedule (more on this later).
Watch out for the gaps in the values
The constant using the value (1 << 14) is commented and not implemented which is why it is not present in the table above.

According to that comment, it was supposed to be a third kind of ranged attack (bits_COND_CAN_RANGE_ATTACK3).

Likewise, there is no constants between (1 << 23) and (1 << 27).
It is worth mentioning that bits_COND_ALL_SPECIAL exists and is a shortcut to include both special conditions ((bits_COND_SPECIAL1 | bits_COND_SPECIAL2)).

Same with bits_COND_CAN_ATTACK which includes all attack bits ((bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK2)).

These are common methods used to handle condition bits with an added description of what they do:
// Returns the conditions ignored by this monster (useful to ignore flinching while attacking for example)
virtual int IgnoreConditions();

// Set the condition(s) specified in "iConditions" for this monster
inline void SetConditions(int iConditions)
{
    m_afConditions |= iConditions;
}

// Clear the condition(s) specified in "iConditions" for this monster
inline void ClearConditions(int iConditions)
{
    m_afConditions &= ~iConditions;
}

// Returns true if any condition in "iConditions" is met for this monster, false otherwise
inline bool HasConditions(int iConditions)
{
    if (m_afConditions & iConditions)
        return true;

    return false;
}

// Returns true if all conditions in "iConditions" are met for this monster, false otherwise
inline bool HasAllConditions(int iConditions)
{
    if ((m_afConditions & iConditions) == iConditions)
        return true;

    return false;
}

Sound bits

The purpose of the CBaseMonster::Listen() method (called by CBaseMonster::RunAI()) is to make the monster hear sounds it could be interested in and classify them using sound bits such as bits_COND_HEAR_DANGER, meaning "I heard a dangerous sound". They work in the same way as condition bits.
What is that stench?
You likely know that monsters can "smell" and react to it like Barney. Scientists will comment on bad scents and bullsquids can eat headcrab carcasses.

Valve made the choice of bolting the "smell system" onto the sound stuff, because they kinda work the same way and it was easy to implement it that way, hence why this is also done in CBaseMonster::Listen().
Here is a table of common sound bits (and "smell bits") and what they mean (soundent.h):
Constant Value What it means to the monster
bits_SOUND_NONE 0 Nothing worth needing my attention.
bits_SOUND_COMBAT (1 << 0) I hear combat, could be explosions or gunshots.
bits_SOUND_WORLD (1 << 1) I hear doors opening/closing, glass breaking.
bits_SOUND_PLAYER (1 << 2) I hear the player making noise (running, walking, jumping...)
bits_SOUND_CARCASS (1 << 3) I smell a dead body.
bits_SOUND_MEAT (1 << 4) I smell gibs or pork chop.
bits_SOUND_DANGER (1 << 5) I hear danger such as a grenade bouncing, barrel about to explode...
bits_SOUND_GARBAGE (1 << 6) I smell trash cans, banana peels, old fast food bags.
Likewise, remember that monsters may use their own.

One particular method to note here is int CBaseMonster::ISoundMask() which returns all the bits that the monster should care about that could influence its behavior. There is also bool CSound::FIsSound() and bool CSound::FIsScent() to distinguish between sound and smell bits.

You also might have seen calls to CSoundEnt::InsertSound(int iType, const Vector &vecOrigin, int iVolume, float flDuration) in various places of the HL SDK that is used to insert sounds (and smells) in the world for monsters to notice.

Memory

All monsters have a m_afMemory attribute which acts as memory for the monster to remember various events. They also work like condition and sound/smell bits.

Again, here is a table of common memory bits (monsters.h):
Constant Value What it means to the monster
bits_MEMORY_PROVOKED (1 << 0) A friendly (likely the player) provoked me (likely through intended friendly fire).
bits_MEMORY_INCOVER (1 << 1) I am in a spot for cover.
bits_MEMORY_SUSPICIOUS (1 << 2) A friendly (likely the player) harmed me but it was an accident, I hope it doesn't happen again.
bits_MEMORY_PATH_FINISHED (1 << 3) I finished moving on a specific path (used by Big Momma only).
bits_MEMORY_ON_PATH (1 << 4) I'm moving on a specific path.
bits_MEMORY_MOVE_FAILED (1 << 5) I already failed to move somewhere.
bits_MEMORY_FLINCHED (1 << 6) I already flinched.
bits_MEMORY_KILLED (1 << 7) I'm already dead (Valve recognized this as a programming hack).
bits_MEMORY_CUSTOM4 (1 << 8) Fourth special memory specific to my type of monster.
bits_MEMORY_CUSTOM3 (1 << 9) Third special memory specific to my type of monster.
bits_MEMORY_CUSTOM2 (1 << 10) Second special memory specific to my type of monster.
bits_MEMORY_CUSTOM1 (1 << 11) First special memory specific to my type of monster.
You probably guessed already: monsters may have their own as well.

Barney is going to help us demonstrate the usage of memory for monsters: if he is not in combat and you hurt him, he's going to remember that you provoked him (bits_MEMORY_PROVOKED) making him stopping following you and try to kill you.

If he is in combat and you "accidentally" hurt him, he is going to get suspicious of you (bits_MEMORY_SUSPICIOUS) and remind you he's a friendly. If you do it again, he will get provoked (bits_MEMORY_PROVOKED) and you already know what is going to happen.

As usual, there are methods to manipulate all of this (with added description):
// Store the bit(s) of memory specified in "iMemory" for this monster
inline void Remember(int iMemory)
{
    m_afMemory |= iMemory;
}

// Remove the bit(s) of memory specified in "iMemory" for this monster
inline void Forget(int iMemory)
{
    m_afMemory &= ~iMemory;
}

// Returns true if this monster remember anything in "iMemory", false otherwise
inline bool HasMemory(int iMemory)
{
    if (m_afMemory & iMemory)
        return true;

    return false;
}

// Returns true if this monster remember everything in "iMemory", false otherwise
inline bool HasAllMemories(int iMemory)
{
    if ((m_afMemory & iMemory) == iMemory)
        return true;

    return false;
}

Capabilities

A capability basically describes what a monster can and can't do. It is stored in the m_afCapability attribute and works the same as the other kinds of bits.

Here is a table of the common ones (cbase.h):
Constant Value What it means to the monster
bits_CAP_DUCK (1 << 0) I can duck.
bits_CAP_JUMP (1 << 1) I can jump.
bits_CAP_STRAFE (1 << 2) I can strafe.
bits_CAP_SQUAD (1 << 3) I can join, form and communicate in a squad.
bits_CAP_SWIM (1 << 4) I can swim.
bits_CAP_CLIMB (1 << 5) I can climb ladders, ropes.
bits_CAP_USE (1 << 6) I can open doors, push buttons, pull levers.
bits_CAP_HEAR (1 << 7) I can hear sounds.
bits_CAP_AUTO_DOORS (1 << 8) I can use automated doors.
bits_CAP_OPEN_DOORS (1 << 9) I can open "manual" doors.
bits_CAP_TURN_HEAD (1 << 10) I can turn my head around (if my bone controller at index 0 is correct).
bits_CAP_RANGE_ATTACK1 (1 << 11) I can perform one kind of ranged attack.
bits_CAP_RANGE_ATTACK2 (1 << 12) I can perform another kind of ranged attack.
bits_CAP_MELEE_ATTACK1 (1 << 13) I can perform one kind of melee attack.
bits_CAP_MELEE_ATTACK2 (1 << 14) I can perform another kind of melee attack.
bits_CAP_FLY (1 << 15) I can fly.
When the monster is being "initialized" , the "common" monsters code will automatically set the attack capabilities by checking if the monster's model has the activities setup (more on that later). In other words, if the model has an ACT_MELEE_ATTACK1 activity

Again, a monster might have its own capabilities. There is also bits_CAP_DOORS_GROUP available that is a shortcut for bits_CAP_USE, bits_CAP_AUTO_DOORS and bits_CAP_OPEN_DOORS together.

One important thing when setting up capabilities, you don't need to set the "can do X attack" bits, the common monsters code will handle that for you when the monster itself is being "initialized" (CBaseMonster::InitMonster() called at the end of the monster's Spawn() method). It achieves this by querying the monster's model for activities (more on that in a little bit). In other words, if the model has at least one sequence tied to the ACT_MELEE_ATTACK1 activity, then bits_CAP_MELEE_ATTACK1 will automatically be set, etc...

However, for the other bits (use, fly, doors, turn head...), you have to set these ones manually in the monster's Spawn() method in the m_afCapability attribute.

There are no methods to manipulate capabilities. If for some reason you need to query if the monster is capable of something, you check directly if its bit is set (if (m_afCapability & bits_CAP_SOMETHING)).

Monster state

All monsters have a monster state stored in m_MonsterState generally describing their current state.

That state impacts several things such as task and schedule selection, some generic behavior, behavior when performing scripted events (aiscripted_sequence/scripted_sequence) and so on.

Here is a table of possible states and what they mean (util.h):
Constant Value What does the monster do?
MONSTERSTATE_NONE 0 Nothing.
MONSTERSTATE_IDLE 1 Idling.
MONSTERSTATE_COMBAT 2 In combat.
MONSTERSTATE_ALERT 3 Staying alert.
MONSTERSTATE_HUNT 4 On the hunt (doesn't seems to be used).
MONSTERSTATE_PRONE 5 Being grabbed by a barnacle or repelling down a rope (human grunt).
MONSTERSTATE_SCRIPT 6 Performing a script (aiscripted_sequence/scripted_sequence).
MONSTERSTATE_PLAYDEAD 7 Play dead (doesn't seems to be used).
MONSTERSTATE_DEAD 8 Dying/dead.

Activities

An activity allows tying sequences/animations in the model (MDL) to an action in the game code like idling, running, walking and so on.

If you look at any monster model with Solokiller's Half-Life Asset Manager (decompiling the model and looking at the QC also works), you can see when browsing the sequences/animations that they may or may not be tied to a specific activity. Idle sequences/animations are likely tied to ACT_IDLE, walking sequences/animations are likely tied to ACT_WALK and so on.

Sometimes, they are not tied to an activity but they may be used by the game code in a different way, if you look at certain monsters code, you can see the usage of int CBaseAnimating::LookupSequence( const char *label ) instead of int CBaseAnimating::LookupActivity( int activity ). Sequences/animations not tied to an activity are also likely used for scripting purposes (aiscripted_sequence/scripted_sequence).

It is important to note that activities are tied to an index through an "activity map" (activitymap.h). Here is a list of common activities (activity.h):
Constant Value What it means to the monster
ACT_RESET 0 Set m_Activity to this invalid value to force a reset to m_IdealActivity.
ACT_IDLE 1 Normal idling.
ACT_GUARD 2 Idling in a guarding stance.
ACT_WALK 3 Normal walking.
ACT_RUN 4 Normal running.
ACT_FLY 5 Fly (and flap if appropriate).
ACT_SWIM 6 Swim.
ACT_HOP 7 Do a vertical jump.
ACT_LEAP 8 Do a long forward jump.
ACT_FALL 9 Fall.
ACT_LAND 10 Land from falling or after a jump.
ACT_STRAFE_LEFT 11 Strafe to the left.
ACT_STRAFE_RIGHT 12 Strafe to the right.
ACT_ROLL_LEFT 13 Tuck and roll to the left.
ACT_ROLL_RIGHT 14 Tuck and roll to the right.
ACT_TURN_LEFT 15 Turn quickly to the left while being stationary.
ACT_TURN_RIGHT 16 Turn quickly to the right while being stationary.
ACT_CROUCH 17 Crouch down from a standing position.
ACT_CROUCHIDLE 18 Stay crouched (loops).
ACT_STAND 19 Stand up from a crouched position.
ACT_USE 20 Use something.
ACT_SIGNAL1 21 First way of making a signal.
ACT_SIGNAL2 22 Second way of making a signal.
ACT_SIGNAL3 23 Third way of making a signal.
ACT_TWITCH 24 Twitch.
ACT_COWER 25 Cower.
ACT_SMALL_FLINCH 26 Do a small flinch after a light damage.
ACT_BIG_FLINCH 27 Do a big flinch after a heavy damage.
ACT_RANGE_ATTACK1 28 Do the first type of ranged attack.
ACT_RANGE_ATTACK2 29 Do the second type of ranged attack.
ACT_MELEE_ATTACK1 30 Do the first type of melee attack.
ACT_MELEE_ATTACK2 31 Do the second type of melee attack.
ACT_RELOAD 32 Reload the firearm.
ACT_ARM 33 Draw the firearm.
ACT_DISARM 34 Holster the firearm.
ACT_EAT 35 Eat some tasty food (loop).
ACT_DIESIMPLE 36 Simple death.
ACT_DIEBACKWARD 37 Die and fall backward.
ACT_DIEFORWARD 38 Die and fall forward.
ACT_DIEVIOLENT 39 Violent death.
ACT_BARNACLE_HIT 40 Barnacle tongue hits a monster.
ACT_BARNACLE_PULL 41 Barnacle is lifting the monster (loop).
ACT_BARNACLE_CHOMP 42 Barnacle latches on to the monster.
ACT_BARNACLE_CHEW 43 Barnacle is holding the monster in its mouth (loop).
ACT_SLEEP 44 Take a nap.
ACT_INSPECT_FLOOR 45 Look at something on or near the floor.
ACT_INSPECT_WALL 46 Look at something directly ahead of you (doesn't have to be a wall or on a wall).
ACT_IDLE_ANGRY 47 Pissed off idling (loop).
ACT_WALK_HURT 48 Wounded walking (loop).
ACT_RUN_HURT 49 Wounded running (loop).
ACT_HOVER 50 Idle while in flight.
ACT_GLIDE 51 Fly (don't flap).
ACT_FLY_LEFT 52 Turn left in flight.
ACT_FLY_RIGHT 53 Turn right in flight.
ACT_DETECT_SCENT 54 Smells a scent carried by the air.
ACT_SNIFF 55 Sniff something in front of the monster.
ACT_BITE 56 Eat something in a single bite (the difference with ACT_EAT is that this one does not loop).
ACT_THREAT_DISPLAY 57 Demonstrate without attacking that I'm angry (yelling for example).
ACT_FEAR_DISPLAY 58 Just saw something scary of feared.
ACT_EXCITED 59 Show excitement (like seeing some very tasty food to eat).
ACT_SPECIAL_ATTACK1 60 First special attack.
ACT_SPECIAL_ATTACK2 61 Second special attack.
ACT_COMBAT_IDLE 62 Agitated idle.
ACT_WALK_SCARED 63 Scared walking.
ACT_RUN_SCARED 64 Scared running.
ACT_VICTORY_DANCE 65 Do a victory dance (after killing a player for example).
ACT_DIE_HEADSHOT 66 Die from a hit in the head.
ACT_DIE_CHESTSHOT 67 Die from a hit in the chest.
ACT_DIE_GUTSHOT 68 Die from a hit in the gut.
ACT_DIE_BACKSHOT 69 Die from a hit in the back.
ACT_FLINCH_HEAD 70 Flinch from a hit in the head.
ACT_FLINCH_CHEST 71 Flinch from a hit in the chest.
ACT_FLINCH_STOMACH 72 Flinch from a hit in the stomach.
ACT_FLINCH_LEFTARM 73 Flinch from a hit in the left arm.
ACT_FLINCH_RIGHTARM 74 Flinch from a hit in the right arm.
ACT_FLINCH_LEFTLEG 75 Flinch from a hit in the left leg.
ACT_FLINCH_RIGHTLEG 76 Flinch from a hit in the right leg.
We have an intruder
ACT_RESET is the only activity that is not part of the activity map due to its special behavior.

It is used when the monster is being "restored", by Big Momma when she need to play a sequence (animation) at a specific "node" and by reload tasks for humans with firearms.
Surprisingly, no monsters have their own activities.

It is possible to have multiple sequences/animations tied to a same activity. In that scenario, the algorithm for choosing a sequence (simplified) is: An use case for this is choosing a random idle sequence/animation from three possible choices whenever a monster is idling.

All monsters have three attributes named m_Activity, m_IdealActivity and m_movementActivity. They indicate the current activity, ideal next one and the one to use for movement respectively. For methods, there are CBaseMonster::SetActivity(Activity newActivity) and CBaseMonster::Stop() that you might find useful.
It is very dangerous to manipulate activities
They are common to all monsters, so if, for whatever reason, you need to add, edit and delete any of them, do it with extreme caution.

You have to treat them and the activity map in the same way you would treat an "exposed C++ interface". If you add a new activity in the middle of the map for example, you will break all monsters unless you update everything including all monsters MDL files. The same applies for deleting an entry and/or changing the order in the map.

Updating the game code will not be enough, you will also be required to build and use a custom version of StudioMDL, the tool to compile QC files into MDLs. If you do not do that, StudioMDL will not be "aware" of the changes you made.

In a nutshell, making any change to activities and the activity map is "extraordinary", only do it if you really think it is really worth it and if you are aware of the consequences.

Animation events

A model can contain events allowing the entity using the model to execute specific things, such as showing a muzzle flash, playing a sound and more. For monsters, it is a way to communicate with the game code and it is an important aspect to consider.

If you look at the scientist model (models/scientist.mdl), more specifically the give_shot animation, we can see some valuable information: there is a tie to the ACT_MELEE_ATTACK1 activity and there is one animation event tied to this sequence/animation. If we look at the event's information, it is triggered on the 17th frame and has the ID #1.
Wait, "ACT_MELEE_ATTACK1" for giving a syringe shot?
Yes, the name of the activity does not always match the real context of the sequence/animation.

The reason for this and you probably have guessed it is because of activities. Why would you risk breaking all monsters because you added an extra activity (and updated the activity map) only to match the context of a single action for only one kind of monster instead of using an existing activity that the same monster isn't already using?

So do not be surprised if you see things like ACT_EAT being used for a sequence/animation that does not involves "eating".
CBaseMonster::HandleAnimEvent(MonsterEvent_t* pEvent) is the method responsible for handling animation events. If we look at the scientist's version, we can see this code:
// For the context:
// "SCIENTIST_AE_HEAL"      = 1
// "SCIENTIST_AE_NEEDLEON"  = 2
// "SCIENTIST_AE_NEEDLEOFF" = 3
// "NUM_SCIENTIST_HEADS"    = 4

void CScientist::HandleAnimEvent(MonsterEvent_t* pEvent)
{
    switch (pEvent->event)
    {
    case SCIENTIST_AE_HEAL: // Heal my target (if within range)
        Heal();
        break;
    case SCIENTIST_AE_NEEDLEON:
        {
        int oldBody = pev->body;
        pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1;
        }
        break;
    case SCIENTIST_AE_NEEDLEOFF:
        {
        int oldBody = pev->body;
        pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0;
        }
        break;
    default:
        CTalkMonster::HandleAnimEvent(pEvent);
    }
}
Taking again our event as an example, it will call CBaseMonster::HandleAnimEvent(MonsterEvent_t* pEvent) with a value of "1" for pEvent->event thus calling Heal() to heal the player (if conditions are still met). You can also note that there are animation events to show and hide the syringe itself as well. Other examples includes shooting firearms, shooting projectiles, throwing grenades.

Animation events may be common to several entities or specific to a particular one.

Other attributes

There are other attributes common to all monsters that are not mentioned (yet for some of them) in this page. However, they do deserve to be known but they don't need a dedicated section to explain what they actually do.

Here is a table of some of them (basemonster.h):
Attribute Description
EHANDLE m_hEnemy Current enemy.
EHANDLE m_hTargetEnt Current target (to follow or to move to).
EHANDLE m_hOldEnemy[MAX_OLD_ENEMIES] Old enemies (MAX_OLD_ENEMIES = 4).
Vector m_vecOldEnemy[MAX_OLD_ENEMIES] Position of old enemies.
float m_flFieldOfView Field of view of the monster (not in degrees, 0.5f is 180 degrees to give an idea of the scale)
float m_flWaitFinished Game time to tell the monster that the wait is over (defined by waiting tasks).
float m_flMoveWaitFinished Game time to tell the monster that the wait before doing any kind of movement is over.
int m_LastHitGroup Last hitgroup being hit (head, chest, stomach...)
Schedule_t *m_pSchedule Current schedule.
Vector m_vecEnemyLKP Last known position of the current enemy.
int m_cAmmoLoaded How much ammo the firearm has.
float m_flNextAttack Time before making a new attack.
int m_bloodColor Color of the blood when being hurt (red, yellow...)
int m_failSchedule Schedule to use in case of a task failure.
float m_flHungryTime Time before eating something again.
float m_flDistTooFar Range in units to determine that the monster is too far from its enemy (see condition bit bits_COND_ENEMY_TOO_FAR).
float m_flDistLook Range in units to acquire enemies.
int m_iTriggerCondition For aiscripted_sequence/scripted_sequence, this is the condition to trigger something (half health, see a player...)
string_t m_iszTriggerTarget For aiscripted_sequence/scripted_sequence, this is the name of the entity to trigger.
Vector m_HackedGunPos The position of the firearm relative to the monster's origin.

Recap

This page has a lot of information to digest so you might want a recap of everything we have seen so far: Here is also a simplified flow chart of the lifecycle of a monster:
User posted image

Comments

You must log in to post a comment. You can login or register a new account.