Activating and Fixing AI In Coop Games: Difference between revisions
mNo edit summary |
|||
Line 242: | Line 242: | ||
== Late Precaching == | == Late Precaching == | ||
You may notice that you keep getting assertions if you are running in debug mode. In order to fix this the best | You may notice that you keep getting assertions if you are running in debug mode. In order to fix this the best is to add a call to PRECACHE_REGISTER underneath the area where you link an entity to a class. For example, for npc_zombie one would place PRECACHE_REGISTER(npc_zombie); underneath LINK_ENTITY_TO_CLASS( npc_zombie, CZombie ) in '''npc_zombie.cpp''' | ||
{{otherlang:en}} | {{otherlang:en}} |
Revision as of 10:55, 30 July 2006

Introduction
This article is an updated version (per 20-03-06) of the previous tutorial Activating AI In Coop Games. Though the previous article allowed you to enable AI in multiplayer games, humanoid NPCs like Combine Soldiers could not attack the player. It is annoying, isn't it? Well, this is time to get over it!
The Relationship Table
Creating the Table
First of all, we should define the NPCs relationship table. Open the BaseEntity.h file, then in the enum Class_T definition, after the #elif defined( CSTRIKE_DLL ) block, add this:
//*** INITS NPCs' RELATION SHIP TABLE FOR HL2DM #elif defined ( HL2MP_DLL || HL2MP ) // For CLASSIFY enum Class_T { CLASS_NONE=0, CLASS_PLAYER, CLASS_PLAYER_ALLY, CLASS_PLAYER_ALLY_VITAL, CLASS_ANTLION, CLASS_BARNACLE, CLASS_BULLSEYE, CLASS_CITIZEN_PASSIVE, CLASS_CITIZEN_REBEL, CLASS_COMBINE, CLASS_COMBINE_GUNSHIP, CLASS_CONSCRIPT, CLASS_HEADCRAB, CLASS_MANHACK, CLASS_METROPOLICE, CLASS_MILITARY, CLASS_SCANNER, CLASS_STALKER, CLASS_VORTIGAUNT, CLASS_ZOMBIE, CLASS_PROTOSNIPER, CLASS_MISSILE, CLASS_FLARE, CLASS_EARTH_FAUNA, NUM_AI_CLASSES }; //***
Loading the Table
All right! Now we have to tell the Game Rules to use this relationship table when we start a HL2DM game. Open the hl2mp_gamerules.h file. Under the public: tab, add this:
//***INITS NPCs' RELATION SHIP TABLE FOR HL2DM #ifndef CLIENT_DLL void InitDefaultAIRelationships( void ); #endif //***
Now open the hl2mp_gamerules.cpp file. Then, at the very bottom of the file, add this piece of code:
//------------------------------------------------------------------------------ // Purpose : Initialize all default class relationships // Input : // Output : //------------------------------------------------------------------------------ //*** INITS NPCs' RELATION SHIP TABLE FOR HL2DM #ifndef CLIENT_DLL void CHL2MPRules::InitDefaultAIRelationships( void ) { //Copy contents of the hl2_gamerules InitDefaultAIRelationships to here } #endif //***
Let's finish by adding this in the CHL2MPRules::CHL2MPRules() function:
//***INITS NPCs' RELATION SHIP TABLE FOR HL2DM InitDefaultAIRelationships(); //***
Making Humanoid NPCs Shoot
Weapons Animations Code
First, find each weapon SP code ( located in the \dlls\hl2_dll\ folder ), then move all the animations declarations located in the SP-acttable_t function to the HL2MP-acttable_t structure. See example bellow:
acttable_t CWeaponSMG1::m_acttable[] = { //HL2DM ANIMATIONS LIST START { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SMG1, false }, ( ... ) { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, false }, //HL2MP ANIMATIONS LIST END //HL2SP ANIMATIONS LIST START { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true }, { ACT_RELOAD, ACT_RELOAD_SMG1, true }, { ACT_IDLE, ACT_IDLE_SMG1, true }, (...) { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, //HL2SP ANIMATIONS LIST END }
Add AI-support to weapons
Open the weapon_hl2mpbase.h file and add the following code before class CHL2MP_Player;:
#ifndef CLIENT_DLL #include "AI_BaseNPC.h" #endif
Other Weapons Modifications
Now you have to edit each weapon code by adding a few lines of code in their class definition. In each weapon class, add those declarations after something like the following code:
class CWeapon**** : public CBaseHL2MPCombatWeapon { public
AFTER THIS CODE, add this one:
#ifndef CLIENT_DLL //ALLOW NPCS TO USE THE WEAPON TO SHOOT THEIR ENEMIES int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } //HANDLES AI'S ACTIVITIES void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); #endif

Models Activity Fix
It has been suggested on some forums to modify the SetActivity() function code. Thus, in the basecombatweapon_shared.cpp file, find the SetActivity() function, and then replace:
//Adrian: Oh man... #if !defined( CLIENT_DLL ) && defined( HL2MP ) SetModel( GetWorldModel() ); #endif
with
if ( GetOwner()->IsPlayer() ) SetModel( GetWorldModel() );
Proceed similarily after the other comment, so you replace:
//Adrian: Oh man again... #if !defined( CLIENT_DLL ) && defined( HL2MP ) SetModel( GetViewModel() ); #endif
with
if ( GetOwner()->IsPlayer() ) SetModel( GetViewModel() );
According to Garry, it fixes animation not playing correctly
Reducing Network traffic
See CBaseFlex for reason of the following modification
src\dlls\BaseFlex.h
Replace CNetworkArray( float, m_flexWeight, 64 );
with CNetworkArrayForDerived( float, m_flexWeight, 64 );
CNetworkArrayForDerived
will allow you to send m_flexWeight to the client for derived classes if needed (acting NPCs)
src\dlls\BaseFlex.cpp
Remove:
SendPropArray3 (SENDINFO_ARRAY3(m_flexWeight), SendPropFloat(SENDINFO_ARRAY(m_flexWeight), 12, SPROP_ROUNDDOWN, 0.0f, 1.0f ) ),
Replace m_flexWeight.Set( index, value );
with m_flexWeight[index] = value;
Ammotypes
Well, if the NPCs kill you instantly, here's the tip to fix it. In the hl2mp_gamerules.cpp file, replace the whole CAmmoDef *GetAmmoDef() function to this:
CAmmoDef *GetAmmoDef() { static CAmmoDef def; static bool bInitted = false; if ( !bInitted ) { bInitted = true; //*** HL2SP DEFINITIONS def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_ar2", "sk_npc_dmg_ar2", "sk_max_ar2", BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("AlyxGun", DMG_BULLET, TRACER_LINE, "sk_plr_dmg_alyxgun", "sk_npc_dmg_alyxgun", "sk_max_alyxgun", BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("Pistol", DMG_BULLET, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_pistol", "sk_npc_dmg_pistol", "sk_max_pistol", BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("SMG1", DMG_BULLET, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_smg1", "sk_npc_dmg_smg1", "sk_max_smg1", BULLET_IMPULSE(200, 1225), 0 ); def.AddAmmoType("357", DMG_BULLET, TRACER_LINE_AND_WHIZ, " sk_plr_dmg_357", "sk_npc_dmg_357", "sk_max_357", BULLET_IMPULSE(800, 5000), 0 ); def.AddAmmoType("XBowBolt", DMG_BULLET, TRACER_LINE, "sk_plr_dmg_crossbow", "sk_npc_dmg_crossbow", "sk_max_crossbow", BULLET_IMPULSE(800, 8000), 0 ); def.AddAmmoType("Buckshot", DMG_BULLET | DMG_BUCKSHOT, TRACER_LINE, "sk_plr_dmg_buckshot", "sk_npc_dmg_buckshot", "sk_max_buckshot", BULLET_IMPULSE(400, 1200), 0 ); def.AddAmmoType("RPG_Round", DMG_BURN, TRACER_NONE, "sk_plr_dmg_rpg_round", "sk_npc_dmg_rpg_round", "sk_max_rpg_round", 0, 0 ); def.AddAmmoType("SMG1_Grenade", DMG_BURN, TRACER_NONE, "sk_plr_dmg_smg1_grenade", "sk_npc_dmg_smg1_grenade", "sk_max_smg1_grenade", 0, 0 ); def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, "sk_plr_dmg_grenade", "sk_npc_dmg_grenade", "sk_max_grenade", 0, 0); def.AddAmmoType("Thumper", DMG_SONIC, TRACER_NONE, 10, 10, 2, 0, 0 ); def.AddAmmoType("Gravity", DMG_CLUB, TRACER_NONE, 0, 0, 8, 0, 0 ); def.AddAmmoType("Battery", DMG_CLUB, TRACER_NONE, NULL, NULL, NULL, 0, 0 ); def.AddAmmoType("GaussEnergy", DMG_SHOCK, TRACER_NONE, "sk_jeep_gauss_damage", "sk_jeep_gauss_damage", "sk_max_gauss_round", BULLET_IMPULSE(650, 8000), 0 ); // hit like a 10kg weight at 400 in/s def.AddAmmoType("CombineCannon", DMG_BULLET, TRACER_LINE, "sk_npc_dmg_gunship_to_plr", "sk_npc_dmg_gunship", NULL, 1.5 * 750 * 12, 0 ); // hit like a 1.5kg weight at 750 ft/s def.AddAmmoType("AirboatGun", DMG_AIRBOAT, TRACER_LINE, "sk_plr_dmg_airboat", "sk_npc_dmg_airboat", NULL, BULLET_IMPULSE(10, 600), 0 ); def.AddAmmoType("StriderMinigun", DMG_BULLET, TRACER_LINE, 5, 5, 15, 1.0 * 750 * 12, AMMO_FORCE_DROP_IF_CARRIED ); // hit like a 1.0kg weight at 750 ft/s def.AddAmmoType("HelicopterGun", DMG_BULLET, TRACER_LINE_AND_WHIZ, "sk_npc_dmg_helicopter_to_plr", "sk_npc_dmg_helicopter", "sk_max_smg1", BULLET_IMPULSE(400, 1225), AMMO_FORCE_DROP_IF_CARRIED | AMMO_INTERPRET_PLRDAMAGE_AS_DAMAGE_TO_PLAYER ); def.AddAmmoType("AR2AltFire", DMG_DISSOLVE, TRACER_NONE, 0, 0, "sk_max_ar2_altfire", 0, 0 ); //*** } return &def; }
Let's Finish
Import skills data
Few people noticed that NPCs were not causing any damage to the player and/or that they could die instantly. This issue is simply caused by the fact the file skill.cfg is missing from the "\cfg" directory in the mod-dir. To fix it, get the skill.cfg file located in the Half-Lfe 2 folder.
Models Animations
You have to replace the HL2DM NPCS animations by the HL2 ones. Indeed HL2DM overrides the NPCs animations - used for the player models - so it breaks the AI. Thus, you will have to use other models files for the players. You should hexedit the HL2DM player models & the animations to make it tight. You will also have to put the HL2 original models in your \models folder. Finally, do not forget to modify the code related to the player models list in the hl2mp_player.cpp file.
If you don't want to waste time hex-editing the model files one by one, then you can download the whole package here .
Fixing other enemies/NPCs
For example, the hopper still won't work. Normally hoppers are only used when there's 1 player in the game and it's code should be edited for multiple players. The same goes for citizens.
Late Precaching
You may notice that you keep getting assertions if you are running in debug mode. In order to fix this the best is to add a call to PRECACHE_REGISTER underneath the area where you link an entity to a class. For example, for npc_zombie one would place PRECACHE_REGISTER(npc_zombie); underneath LINK_ENTITY_TO_CLASS( npc_zombie, CZombie ) in npc_zombie.cpp