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 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:
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:
CBaseMonster
(basemonster.h
) being the very basic one that is itself the base for the others two.
CSquadMonster
(squadmonster.h
) that provide squad functionality to the monster.
CTalkMonster
(talkmonster.h
) for talkative monsters with dialogues such as questions, answers and so on.
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:
- Calling
CBaseMonster::Look()
to gather visual information like a monster has spotted an enemy, friend, nemesis.
- Calling
CBaseMonster::Listen()
to gather audio information like footsteps, grenade explosions, shooting firearms.
- Seeking enemies with
bool CBaseMonster::GetEnemy()
and determine the best one to attack based on relationship and other factors.
- Check ammo if needed with
CBaseMonster::CheckAmmo()
, it is overridden and used by human grunts only.
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). |
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.
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. |
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:
- For each sequence in the model...
- ...ignore it if it's not tied to the requested activity.
- Add it's weight to a counter (total weight).
- If that counter (total weight) is 0 or a randomly generated number between 0 and that total weight is less than the sequence's weight, then that sequence is remembered to be chosen (unless a next iteration of the loop overrides it).
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.
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.
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:
- There are multiple base classes for monsters providing different functionality, but they all share a common base.
- A monster does not always involve an AI (player).
- Condition bits are used by monsters to impact their behavior ("I see a new enemy", "I can perform that kind of attack", "I took light or heavy damage"...)
- Sound bits (and smell by the way) may be used by monsters to hear sounds (and smell stuff) and classify them (danger, world...)
- Memory bits allow monsters to remember things (Did the player betray me? Did I already flinch?)
- Capability bits tell the monster what it can and cannot do (open doors, swim, fly, climb ropes and ladders...)
- Condition, sound, memory, capability bits as well as tasks and schedules can be common to all monsters or specific to a monster.
- Activities allow to group together sequences/animations by actions (idling, walking...) Changes are exceptional and "fragile".
- Animation events allow the monster's model to communicate with the game code (actually shoot a firearm, throw a grenade...)
Here is also a simplified flow chart of the lifecycle of a monster: