Fixing AI in multiplayer

From Valve Developer Community
Jump to: navigation, search

This article describes the changes that need to be made in order to get NPCs to work in the multiplayer SDK, as there are several key features that need to be re-implemented in a multiplayer context. While it covers the use of the HL2DM SDK, it should be readily convertible to work with the Scratch SDK, by referring to the correct GameRules.

Note that a patch is available for the Orange Box HL2DM source that implements all changes described here, it is listed near the end of the article.

Note that a Fork of the ValveSoftware/source-sdk-2013 codeline on GitHub exists in which these changes have been applied. Cloning this fork might be a good way to get these changes into your project.


There is no relationship table set in HL2DM. Without this table, NPCs won't know which entities to hate/like, and if Combine Soldiers like players, they won't attack them.

This is simple to fix. First go to hl2mp_gamerules.h, under void RestartGame(); add this:

 #ifndef CLIENT_DLL 
 void InitDefaultAIRelationships( void );

Next go to hl2_gamerules.cpp and copy the entire InitDefaultAIRelationships() function. Paste it in hl2mp_gamerules.cpp.

Don't forget to put the copied functions from hl2_gamerules.cpp between #ifndef CLIENT_DLL and #endif.

And change void CHalfLife2::InitDefaultAIRelationships( void ) to void CHL2MPRules::InitDefaultAIRelationships( void ) in hl2mp_gamerules.cpp .

And Voula now you should have working relationships with npcs


Activities & animation events

The weapons have custom activities and animation events used for the AI in HL2. In the HL2DM-versions of the weapons this is removed.

Open the weapon_hl2mpbase.h file and add the following code after the includes:

#ifndef CLIENT_DLL
	#include "AI_BaseNPC.h"

Now open the HL2SP files of the BaseHLBludgeonWeapon, AR2, shotgun, SMG1, crowbar, pistol, stunstick and frag grenade (found in dlls/hl2_dll/). The NPCs in HL2 don't use any other weapons. Each weapon has a Operator_HandleAnimEvent and/or CapabilitiesGet function, which the NPCs use to fire their weapons. Copy these functions and any other functions that are called in Operator_HandleAnimEvent to the HL2DM weaponfiles (found in game_shared/hl2mp/).

Don't forget the headers and to put all copied functions between #ifndef CLIENT_DLL and #endif.

Also, you need to copy the activities. Look for m_acttable[] in the HL2SP-variants and copy them to the HL2DM-variants.


Open basecombatweapon_shared.cpp and look for the function SetActivity. Look for the following code:

//Adrian: Oh man...
#if !defined( CLIENT_DLL ) && defined( HL2MP )
	SetModel( GetWorldModel() );
int sequence = SelectWeightedSequence( act ); 
	// FORCE IDLE on sequences we don't have (which should be many)
	if ( sequence == ACTIVITY_NOT_AVAILABLE )
		sequence = SelectWeightedSequence( ACT_VM_IDLE );
//Adrian: Oh man again...
#if !defined( CLIENT_DLL ) && defined( HL2MP )
	SetModel( GetViewModel() );

The activities can only be retrieved from weapon world models. However the weapons of players are viewmodels. So with this little hack the models are changed to worldmodel, activies are retrieved and changed back to viewmodel. However considering NPCs don't have to see or work with their viewmodels, this is going to be changed:

	//Adrian: Oh man...
	if ( GetOwner()->IsPlayer() )
		SetModel( GetWorldModel() );
	int sequence = SelectWeightedSequence( act ); 
	// FORCE IDLE on sequences we don't have (which should be many)
	if ( sequence == ACTIVITY_NOT_AVAILABLE )
		sequence = SelectWeightedSequence( ACT_VM_IDLE );
	//Adrian: Oh man again...
	if ( GetOwner()->IsPlayer() )
		SetModel( GetViewModel() );


The damage of the weapons need to use the data from skill.cfg. Don't forget the copy HL2's skill.cfg to your mod's cfg directory.

Also, the code needs to be told to use this values. Open hl2_gamerules.cpp again and copy the entire GetAmmoDef function. Replace the same function in hl2mp_gamerules.cpp with the copied code but add the following line:

def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0);

HL2 doesn't have the SLAM and without that line the SLAM won't work anymore.

You need to include hl2_shareddefs.h in hl2mp_gamerules.cpp too. Otherwise you will get two compiler errors C2065: 'DMG_SNIPER': undeclared identifier.

Model animations

Source's humanoid NPC models and deathmatch player models were not designed to be compatible with each other. The player and NPC versions both require different animations, and so depending on the order you mount your game content in (HL2DM before HL2, or vice versa) either humanoid NPCs or player animations will be completely missing, unless you use custom models.

Assuming you don't have new models, then two approaches are open to you.

  1. The first involves duplicating either the NPC or player models, and using a hex editor to direct the model to use a different animation file (eg create a renamed copy of the npc animations, and change NPC models to use that instead)
  2. Alternatively, you can try to "remap" the NPC animation names into player names in the code. A brief discussion of this method can be seen here:

Function calls

Still, much of the AI code is still not designed to be used in multiplayer. All calls to the functions AI_GetSinglePlayer, AI_IsSingleplayer and UTIL_GetLocalPlayer would need fixing. Also pieces of code like if ( gpGlobals->maxClients == 1) and UTIL_PlayerByIndex( 1 ). There are hundreds of these calls, and it's lots of work to fix them all.

Lag compensation

Without lag compensation, when you shoot at a target, you have to take your ping time into account, and aim ahead of it accordingly. So if your ping is 100ms, you have to aim for where the target's head will be in 100ms, rather than where you see it right now.

Clearly this is less than ideal, and so HL2DM incorporates lag compensation, which when calculating whether a bullet hits or not, briefly adjusts the position of all players back by the shooter's ping, so that it calculates the bullet collision based upon exactly what the shooter saw when they fired. This effect is missing for NPCs, but can be duplicated from the player lag compensation code without too much trouble.

The NPC lag compensation article proved too long to include in this tutorial, it is instead located here.

Jerky animations

When ping times are large enough, NPC animation becomes extremely slow and jerky. There's a built in mechanism to correct for this, interpolation. Essentially, it shows all NPCs etc slightly "back in time" by a few hundred milliseconds, essentially giving more buffer time for clients. Its controlled by a ConVar, and it would be best to have a slightly higher value by default, so open c_baseentity.cpp, and find

static ConVar  cl_interp_npcs( "cl_interp_npcs", "0.0", FCVAR_USERINFO, "Interpolate NPC positions starting this many seconds in past (or cl_interp, if greater)" );

Change the second parameter (default value) to 0.25, and now an unusually high ping will be required to distort animations.

static ConVar  cl_interp_npcs( "cl_interp_npcs", "0.25", FCVAR_USERINFO, "Interpolate NPC positions starting this many seconds in past (or cl_interp, if greater)" );

That's 250 milliseconds. Experiment with the net_fakelag console command to determine an optimal value, the higher value you provide, the higher latency users can have and still observe smooth animation. For 250 ms cl_interp_npcs, a net_fakelag of 150 ms still results in smooth animation. The higher a value you provide, however, the further NPCs will operate "in the past" - this only really presents a problem with fast zombies and headcrabs, as the user will appear to take damage while the NPC is still jumping towards them. If you are making a multiplayer zombie mod, it is recommended that you only set the ConVar to around 150, as it would decrease the effect of taking damage before being hit.

Instead of relying on interpolation, UseClientSideAnimation(); could be added to the CAI_BaseNPC constructor (in ai_basenpc.cpp) - however, this significantly dampens NPCs animations, and so is not recommended.


Thanks to the prediction, the blood of NPCs is suppressed. This is because in HL2DM the blood of players is done client-side. This is not the case with NPCs and the prediction needs to "break":

Open util_shared.cpp and look for the function UTIL_BloodDrips. Add to the top of the function:
IPredictionSystem::SuppressHostEvents( NULL );


In BaseCombatCharacter.cpp (Or ai_basenpc.cpp?) find CAI_BaseNPC::Ignite and remove/comment out the #ifdef HL2_EPISODIC section. In npc_BaseZombie.cpp find CNPC_BaseZombie::MakeAISpookySound and remove/comment out its contents.


Including the 'function calls,' there's an awful lot of changes required to implement AI in a multiplayer mod. This patch file implements all the changes described here, (crucially) including correcting all the function calls and lag compensation code - so it should save you a good deal of dull coding work! See the readme for details of how it replaces calls to functions such as UTIL_GetLocalPlayer() & AI_GetSingleplayer(). Users requiring assistance in applying a patch file are directed here.

Due to Megaupload being closed it has been uploaded using which uploads to 8 various file hosting websites Download Here

Another direct link is Here

15th June 2009 HL2MP Patch

Note:This version of the HL2MP patch file has been superseded by the OrangeBox CoOperative Base Modification which contains numerous fixes (alongside other game enhancements which can be removed/disabled). You will notice that this patch is a very early alpha version of OBCO (then called ShadowSource). If you feel up to the challenge you can also 'patch' your hl2mp modification manually by using the following tutorial (Please note that I no longer guarantee that this AI patch contains all the AI fixes included in the latest OBCO version!):

If you don't wish to use OBCO or the manual tutorial, then here is the old patch.

The patch (which was made using the original Winston patch via copy & paste) can be download from MegaUpload. Please note that this is the basic AI patch added to the new SDK, it behaves like the old patch.

There is a small bug that crept in on each weapon_*.cpp file. That is that the ACT_ items are in #ifndef client_dll sections. This is incorrect, they (and the IMPLEMENT_ACTTABLE) should be outside these. If you don't fix these, then even if you get custom player animations to work, their activities won't be called so they'll continue to be t-posed with no animations.

For example in the weapon_ar2:

acttable_t	CWeaponAR2::m_acttable[] = 
	{ ACT_MP_STAND_IDLE,				ACT_HL2MP_IDLE_AR2,					false },

Should be:

acttable_t	CWeaponAR2::m_acttable[] = 
	{ ACT_MP_STAND_IDLE,				ACT_HL2MP_IDLE_AR2,					false },

Note:Everything which was modified by the original AI patch is commented with either "//AI Patch" or "// AI Patch".
Warning:Because this comes from code I'd been modifying for my own requirements, there are additional changes. To remove my edits just search for //Modification

The files with modifications are:

  • client/episodic/c_prop_scalable.cpp
  • client/in_joystick.cpp
  • server/episodic/ep2_gamestats.cpp
  • server/player.cpp
  • shared/gamemovement.cpp
  • shared/gamemovement.h

Finally additional GCF mounts are in client/cdll_client_int.cpp and server/gameinterface.cpp , which are commented as // MOUNTING ADDITIONAL GAMES LINES.

Note:If sdk_gamerules.cpp gives errors during compile after applying this patch, you may have to do the Relationships step manually.


While a lot of work is involved, particularly if the patch file is not used, the changes described here should fix most of the AI to a reasonable standard for use in multiplayer. Further changes that will be required, and there is probably room for optimisation in the "function call" fixes, but most NPCs should work well.