Fixing AI in multiplayer
This article describes the changes that need to be made 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 Half-Life 2: Deathmatch SDK, it should be readily convertible to work with any 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 Source 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.
Relationships
There is no relationship table set in Half-Life 2: Deathmatch. 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 );
#endif
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
to void CHalfLife2::InitDefaultAIRelationships( void )
in hl2mp_gamerules.cpp .
void CHL2MPRules::InitDefaultAIRelationships( void )
And Voula now you should have working relationships with npcs
Weapons
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"
#endif
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 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.
SetActivity()
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() );
#endif
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() );
#endif
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, and activities 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() );
Ammotypes
The damage of the weapons need to use the data from skill.cfg
. Don't forget to copy Half-Life 2's skill.cfg
to your mod's cfg
directory.
Also, the code needs to be told to use these 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);
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.
- 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)
- 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: http://forums.steampowered.com/forums/showthread.php?t=752399
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. It's 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 the 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.
Blood
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 );
Zombies
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.
Patch
Including the 'function calls,' there are 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 mirrorcreator.com which uploads to 8 various file hosting websites Download Here
Another direct link is Here
15th June 2009 HL2MP Patch
The patch (which was made using the original Winston patch via copy & paste) can be downloaded 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 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:
ifndef CLIENT_DLL
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 },
//AI Patch
" or "// AI Patch
".//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
.
Conclusion
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.