Article
B00006FXIN

The AI from Half-Life’s SDK in Retrospective

Alex J. Champandard on February 11, 2008

When it was originally released in 1998, Half-life received critical acclaim for its game design — made possible by the underlying artificial intelligence. This kind of impact on the game AI community ranks right up there among the most influential ever.

Even ten years later, looking into the codebase is a great way to learn about designing simple but effective AI systems. The whole AI logic is hard-coded in C++ and isn’t overly object-oriented, so it’s much easier to follow than recent data-driven engines (although it’s not as straightforward to extend).

This article looks into the publicly available SDK for Half-Life 1, analyzing the various aspects of the AI such as the task scheduling system, its state-machine like implementation, and the sensory system. By the end of the article you should have a better idea of how to use these concepts in your game to help its implementation.

Cooperative AI and Monsters

Screenshot 1: Barney the security guard fighting off one of many monsters.

Downloading the Half-Life SDK

Valve’s SDK for Half-Life is very easy to install (compared to F.E.A.R.’s tools) and only requires the original game if you want to start developing mods. Here’s what you need:

  1. Download version 2.3 of the Half-Life SDK, either the source only version with no assets, or grab a copy of the full SDK with the models included.

  2. Extract the file into a directory of your choice, preferably within the game folder if you intend to build mods using the SDK. This will take a few seconds and leave you with a bunch of directories with models and source code.

Half-Life SDK AI Files

Screenshot 2: C++ game code in the Half-Life SDK version 2.3.

Finding Your Way Around the Code

The codebase isn’t as well structured as F.E.A.R. or even Quake 3. There are a few subdirectories but the files aren’t named very consistently, and the implementation of C++ classes is spread over multiple files with little or no correlation between the filenames.

  • In the full SDK, there are two folders that contain the code: Single-Player Source and Multiplayer Source both with similar directory structures.

  • The bulk of the game logic is in the /dll/ subdirectory, where you’ll find all the files required to build hl.dll which is also a framework for mods. This directory also contains the AI code, which comes in a variety of files including the names *monster*.[h,cpp], *ai*.[h,cpp] and other miscelaneous files.

  • You’ll find other directories within the source directory, such as engine containing header files that interface with the main executable (like base entities). The common directory also contains similar low-level files that are shared between the engine and the game code.

If you’re studying or modifying the AI, most of your time will be spent in the /dll/ directory as it contains the behavior for different actors in the game.

Scientist AI

Screenshot 3: An in-game cut-scene including a scientist.

Scheduler and Goal System

In the files schedule.[h,cpp], you’ll find a very simple goal-driven system. It consists of multiple layers of tasks that can be combined procedurally.

Tasks
Tasks are short atomic behaviors that are defined for a specific purpose. For instance, most actors in Half-Life support the following: TASK_WALK_PATH, TASK_CROUCH, TASK_STAND, TASK_GUARD, TASK_STEP_FORWARD, TASK_DODGE_RIGHT, TASK_FIND_COVER_FROM_ENEMY, TASK_EAT, TASK_STOP_MOVING, TASK_TURN_LEFT, TASK_REMEMBER. These are defined as enumerations in the header file, and implemented as C++ methods.
Conditions
Conditions are used to express the situation of an actor in the world. Since everything is hard-coded, conditions can be expressed very compactly as a bitfield, but in this case it limits the different conditions to 32. For example, conditions are COND_NO_AMMO_LOADED, COND_SEE_HATE, COND_SEE_FEAR, COND_SEE_DISLIKE, COND_ENEMY_OCCLUDED, COND_ENEMY_TOOFAR, COND_HEAVY_DAMAGE, COND_CAN_MELEE_ATTACK2, COND_ENEMY_FACING_ME.
Schedules
A schedule is composed out of a series of tasks (with arbitrary parameters), and given a bitfield of conditions to help specify when this schedule invalid. The basic schedule object also has a name to help with debugging.
Goals
On a higher level, goals are made up of multiple schedules. The logic in the goal can select a schedule as necessary based on which task failed, and what the current context is. The goals in Half-Life include GOAL_ATTACK_ENEMY, GOAL_MOVE, GOAL_TAKE_COVER, GOAL_MOVE_TARGET and GOAL_EAT.

The code Valve used is originally derived from Id’s Quake engine, which is still obvious even though the code was converted to C++; files and structs are named similarly.

Scientist AI

Screenshot 4: Combine soldiers on alert in the research facility.

Finite State Machine

In practice, each of these schedules and tasks are connected together using a FSM-like implementation. At the top level, a function in monsterstate.cpp is called to update the AI:

void CBaseMonster :: RunAI ( void );

This in turn called overloaded functions responsible for checking that the current schedule is valid using MaintainSchedule() and picking new ones using GetSchedule(). These can be customized by derived classes depending on their requirements, for example see barney.cpp or scientist.cpp.
At a lower level, functions named StartTask() and RunTask() implement the logic for each of the task identifiers defined in the enum statement. These are implemented in classes derived from CBaseMonster also. In practice, this ends up looking a lot like a state machine implemented as a switch statement.

void CScientist :: RunTask( Task_t *pTask )
{
  switch ( pTask->iTask )
  {
  case TASK_RUN_PATH_SCARED:
    if ( MovementIsComplete() )
      TaskComplete();
    if ( RANDOM_LONG(0,31) < 8 )
      Scream();
    break;

  case TASK_MOVE_TO_TARGET_RANGE_SCARED:
    /* ... */
    break;

  case TASK_HEAL:
    if ( m_fSequenceFinished )
    {
      TaskComplete();
    }
    else
    {
      if ( TargetDistance() > 90 )
        TaskComplete();
      pev->ideal_yaw = UTIL_VecToYaw( /* ... */ );
      ChangeYaw( pev->yaw_speed );
    }
    break;

  default:
    CTalkMonster::RunTask( pTask );
    break;
  }
}

A more typical approach would be to implement each of these case blocks in their own class, but this way it’s much easier to reuse logic from one object to another if necessary — at the cost of modularity.

It’s also interesting to note that the AI maintains two states, one ideal state and one current state. This way, it’s easy for the game code to provide goals to the actors and have them work out the best way to achieve them. It’s an interesting combination of a state machine with a goal-oriented system.

Scientist AI

Screenshot 5: An in-game cut-scene including a scientist.

Sensory System Implementation

In the base monster.[h,cpp] you’ll find the code that gives all actors a sense of sight, smell, and hearing.

void CBaseMonster :: Look ( int iDistance );

The sight function checks various flags, such as SF_MONSTER_PRISONER and SF_MONSTER_WAIT_TILL_SEEN to provide control to the designers when necessary. Things like line of sight, field of view are also taken into account in the equation.

CSound* CBaseMonster :: PBestSound ( void );

The hearing and smell code operate similarly using sound events. There’s a list maintained for objects that deserve the monsters attention, and the sensory system picks the best one to focus on.

Summary and Further Reading

Overall, the source code behind this system, although simple, is very enlightening too look through. If you’re looking for a lightweight implementation of AI decision making, then you could do much worse than this approach. However, you’d probably want to implement each task in its own object, which has become the most typical solution to create more manageable logic in commercial games these days.

Squad AI

Screenshot 6: Emergent squad behaviors in Half-Life.

Other highlights of Half-Life’s AI include the following ideas.

  1. The game code represents waypoints a 3D Vector and a location type only! These tie in to the underlying navigation system, but these can also be used to implement old-school bread crumbs for the monsters to follow.

  2. The original Half-Life surprised many people with their squad behaviors. However, there is no high-level AI that controls these squads, so all the behavior is emergent.

If you’re looking to build something more than a so-called monster in Half-Life, your best bet is to use a bot framework. This will allow you to build multiplayer AI bots that can play in any third-party mod for Half-life. Here’s where you can find them:

If you have any questions or observation about the source code in Half-Life 1, then feel free to post a comment below or in the forums!

Discussion 4 Comments

Andrew on February 11th, 2008

Screenshot 4 is mislabelled :) Would have been nice to see some info on how the behaviours interact - like why suddenly some monsters retreat when damaged, or how soldiers retreat (I presume waypoint nodes are used? LOS checks? ally checks?) But good short overview in any case. Some vast improvements made to the HL1 engine AI are in the Sven co-op mod, well worth checking out (the multiplayer bot AI is good too, perhaps better used in Team Fortress or Counterstrike then deathmatch though).

wisemx on February 12th, 2008

The node system in HL and HL2 are similar however the node system in the Source Engine has evolved with new classes for AI such as "Might see". Nodes can give commands to the NPC, make suggestions, bridge nodes or initiate a script. The script system for AI in relation to NPCs is very flexible and provides three series of commands available but not required which can each trigger individual AI before connecting the current mode script to the next, if needed. Allowing NPCs to bridge, think and or see was a smooth move on Valve's part. Outside all of this are the Trigger Inputs and Outputs that can be attached to each NPC or class of NPC which in the instance of damage can control other actions, like a new set of scripts/behaviors. NPCs can for example have a filter attached to their name or class that will specify who they like, hate or can damage or not. When damaged they can contain an Output that will activate a Hate trigger/filter. There is no end to the mayhem you can create.

alexjc on February 12th, 2008

Andrew, One of those two soldiers in Screenshot 4 actually has/had a scientific background until he realized there was more money to be made as a tool of overly powerful governments in bed with private corporations :-) As far as I can tell, the logic for retreating is engaged when the attacking schedule bails out because its assumptions no longer matches the situation bitfield (that's where conditions are used). Another schedule is chosen that presumably takes into account the retreat, depending on the character. I'm not sure about the waypoint usage though... wisemx, Many thanks for clarifying! :-) Alex

William on February 14th, 2008

The following two presentations (from Valve AI developers*) shine some more light on the Half-Life AI: [URL="http://www.gdconf.com/archives/2003/Leonard_Tom.ppt"]Building AI Sensory Systems: Lessons from Thief and Half-Life[/URL], by Tom Leonard, Valve, Game Developer Conference 2003, Powerpoint presentation on the sensory system design, as applied in two games famous for their AI response to player activity. [URL="http://ai.eecs.umich.edu/people/laird/game-seminar/Liden.ppt"] The Use of Artificial Intelligence in the Computer Game Industry Lars Lidén[/URL], talk at University of Michigan, October 19, 2001 Using Half-Life as an example, Lars discusses the components of an AI system, decision making, tactical analysis and artificial stupidity. *: AFAIK both Tom and Lars joined Valve after Half-Life was completed. Most of the Half-Life AI was created by Steve Bond and some other Valve develoepr (MobyGames' credits aren't helping me here). William

If you'd like to add a comment or question on this page, simply log-in to the site. You can create an account from the sign-up page if necessary... It takes less than a minute!