Half-Life bot development
Building the SDK
- Half-Life SDK
- C++ Basics
HPB Waypoint files
|The Valve Half-Life
The Half-Life SDK contains six subdirectories: cl_dll, common, dlls, engine, pm_shared, and utils. The cl_dll directory contains code used to build the client side dll which controls the HUD functions (displaying sprites, printing messages from "say" (chat) commands, etc). The common directory contains header files common to both the client.dll and the hl.dll (or mp.dll) files. The dlls directory contains code used for the server side dll that interfaces directly with the Half-Life 3D engine (this is the dll that we need to modify to support bots). The engine directory contains include files with parameters and functions used to communicate with the Half-Life 3D engine. The pm_shared directory contains code that handles player movement and this is also shared code between the client.dll and the hl.dll (or mp.dll) files. The utils directory contains source code to some utilities that allow you to examine or create maps, models, and sprites.
The basic method used to create a bot is to create a "virtual" player connected to the server. The simplest way to do this in Half-Life is to create a "bot" object that has the same characteristics and properties that a "player" object has. When we create this bot object, we will need to create code that moves the bot around, fires weapons, and makes decisions about which direction to go, which weapon to select, when to fight and when to run away. We are, in essence, creating an artificial intelligence. All of the code that we will need to modify can be found in the dlls directory.
Reading the header files (*.h) and the source code files (*.cpp) will require that you have some knowledge of C++. You don't necessarily have to be an expert in C++ to understand things in the SDK, but it sure helps. I hope to be able to provide enough information here so that even someone with only a little bit of exposure to C++ can create a bot or modify source code from my bot to suit their own tastes.
Two of the things you have to be able to deal with when using the Half-Life SDK are a 3D coordinate system and vectors. Everything in Half-Life is located within a 3 dimensional space. There is an X, Y and Z coordinate for the location of everything in the game. Some things are at fixed locations (like weapons, ammo, walls, switches, etc.) and other things are changing locations (like players, bots, trains, etc.).
Vectors are used to indicate a direction and magnitude. For example, a vector is used to indicate which direction, and how far, your body will travel when you've wandered too close to a tripmine. A vector is also represented using an X, Y, and Z value, but the values don't correspond to a location in space, instead they correspond to a 3 dimensional direction and distance (or magnitude) from another specified point in space. Vectors can, and often do, contain negative values to indicate that something should travel backwards or downwards from the starting location.
You can create a vector by subtracting 2 points in space. You can add a vector to a point in space to determine the end point in space of the vector. Try not to get too hung up on trying to remember which way is up (+Z or -Z), or whether X is forward and Y is right, or is it, Y is forward and X is right? If you think about it, the X-Y-Z axis orientation depends on your point of view. Left to you will be right to me if we are facing each other. Trying to keep left, right, top and bottom orientation in your head as you are doing things will probably only confuse you. Instead, try to rely on the UTIL functions provided in the Half-Life SDK to do vector arithmetic and let the numbers work themselves out. It's difficult at first and requires a "leap of faith", but once you get the hang of it, you'll realize that you don't really need to know which way is up.
Another thing that you need a fairly good understanding
of is C++ class inheritance. The Half-Life SDK relies on classes (objects)
inheriting from other classes (objects). In the Half-Life SDK many of the
objects in the game (monsters, players, weapons, etc.) all inherit from
one common base class called CBaseEntity. All of the attributes (variables)
and methods (functions) found in CBaseEntity are available to any object
that inherits from CBaseEntity. Nearly everything in the Half-Life SDK
inherits from CBaseEntity. What this means it that all of these objects
have some things in common. An X-Y-Z origin (location), velocity, angles,
size, and movetype (the way it moves) are some of the attributes that all
of these objects have in common. Functions like, Spawn(), IsMoving(), IsAlive(),
Think() and Touch() are all methods that these objects have in common (even
weapons have a Think() function).
Here's some common questions about things in the SDK...
Question: How big is the world within a map in Half-Life?
world in a map can be a maximum of 8191 units high, 8191 units wide, and
8191 units deep. The range is actually -4096 < x < 4096, -4096 <
y < 4096, and -4096 < z < 4096. This information came from the
file "cbase.cpp" using CBaseEntity::IsInWorld() which is used to determine
if an entity is within the world boundries or not.
Question: How big is the player in Half-Life?
player is 72 units high, 32 units wide and 32 units deep. The player is
half this high when ducking (36 units). This information came from the
UTIL_SetSize() function in CBasePlayer::Spawn() in the file "player.cpp".
When the player is not ducking, UTIL_SetSize() uses VEC_HULL_MIN and VEC_HULL_MAX
as the limits of the bounding box for the player. In "util.h" VEC_HULL_MIN
is (-16, -16, -36) and VEC_HULL_MAX is (16, 16, 36) which makes the player
32x32x72 units. Similarly VEC_DUCK_HULL_MIN is (-16, -16, -18) and VEC_DUCK_HULL_MAX
is (16, 16, 18) giving a ducking size of 32x32x36.
Question: How do you know the location of your player within the map?
location of a player can be determined by the field "origin" in the structure
pointed to by "pev". "pev" is a "pointer to an entvars_t structure". The
"entvars_t" structure can be found in file "engine\progdefs.h". "pev" is
an attribute in the CBaseEntity class (found in "cbase.h"). The CBasePlayer
object inherits from CBaseEntity and therefore contains "pev" as well.
If you access "pev->origin" from within the CBasePlayer scope you will
have the origin (location) of the player. This location will be an 3 element
array containing the X, Y and Z coordinate of the player.
Question: How do you find the location of other players within the map?
Answer: You can interate through the list of players that the server keeps using UTIL_PlayerByIndex() found in "util.h". Here's some sample code that shows how to loop through all the players...
// search the world for players...
// skip invalid
// skip dead
or dying players (i.e. not alive)
pPlayer->pev->origin; // get the location
Notice that the index goes from 1 to maxClients
(not starting at zero). Notice how I skip over players that are dead or
dying. The DEAD_NO flag indicates that a player is alive (usually also
checked with "pev->health > 0").
Question: How do you tell which direction a player is facing?
Answer: The entvars_t structure, that "pev" points to, contains 2 variables that are often confused. These variables, "angles" and "v_angle", are vec3_t types (3 element array of floats) indicating angles (-180 to 180 degrees) for the player model. The "angles" variable controls the angles of the model relative to the ground. If one of these angles was set to 45 degrees, the model would lean at a 45 degree angle (like walking into the wind). If the angle were set at 90 degrees, the model would be lying flat on it's stomach or back. The "v_angle" variable is used to indicate the direction the model is facing (or viewing). If you set one of the v_angle to 45 degrees, the model would still be standing upright, but the head would be tilted up or down at a 45 degree angle.
The angles are divided into 3 components: Pitch,
Yaw and Roll. Pitch is the first element of the array (i.e. v_angle).
Yaw is the second element of the array (i.e. v_angle). Roll is the third
element of the array (i.e. v_angle). Pitch is the angle leaning forward
or backward. Yaw is the angle turning left or right. Roll is the angle
tilting to the left or right (like you were spinning on a pole stabbed
through your stomach from front to back, ugh!). You use the "v_angle" pitch,
yaw and roll to determine which way a player is looking (and hence facing).
To cause a player to rotate in place (i.e. spinning around), you would
change the yaw component of "v_angle" (i.e. v_angle).
Question: How do you tell how far away something is from the player?
Answer: To find distances you need to subtract the origin of 2 entities, which will create a vector. You can then find the length of this vector by using the Length() function in the Vector class (found in the file "vector.h"). Here's an example of finding out how far away each player is from you...
float min_distance = 9999.0;
// search the world for players...
// skip invalid
// skip dead
or dying players (i.e. not alive)
// skip "this"
player (i.e. skip myself)
// make a vector
from the other player's origin to my origin
float distance = v_dist.Length( );
When the loop ends, min_distance will contain the distance to the nearest player and pEnemy will point to that player's CBaseEntity class (or it will be NULL indicating that no other players were found).