Adding a new weapon to your mod: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
m (Nesciuse moved page Adding a new weapon to your mod/en to Adding a new weapon to your mod without leaving a redirect: Move en subpage to basepage)
 
(66 intermediate revisions by 12 users not shown)
Line 1: Line 1:
This article will deal with creating a default weapon for your mod.
{{LanguageBar}}
 
 
This article will deal with creating a custom weapon for your mod.
This was based on an existing article which recently was removed:
This was based on an existing article which recently was removed:


{{note|1=This has only been tested with the latest HL2MP OB engine code.}}
{{Note|'''IMPORTANT!''' If you want to achieve this for singleplayer, please see the "Single Player Only" example(s). Multiplayer [[acttable_t|ActTable]] does not include NPC animations and other events!}}
{{note|1=If you want to achieve this for singleplayer you should check the [[Activity List]] for a working acttable! Or else you and your NPC's will be t-posing all over the place...}}
{{Note|The HL2MP portions have only been tested with the latest HL2MP OB engine code.}}
{{Note|This tutorial covers code implementation and scripting ONLY. For sound and model creation, check out modeling and sound creation.}}
 
== The Code (Single Player Only) ==
 
Before you start you will need to either create a new blank '''.cpp''' or copy an existing entity (such as [[weapon_smg1]]) to use as a template. Using an existing entity makes for easy copy/paste work but it is risky as it is easy to miss definitions or calls to the original weapon entity. Also, be sure to back up any core HL2 files if you are using them as a template BEFORE saving any changes. In either case, your new weapon '''.cpp''' file should read '''weapon_<yourweaponname>.cpp''', place it on the Server side of your project.
In this example, we will use '''weapon_mp5''' throughout the SP part of the tutorial.


{{Note|Remember to change <code>CWeaponMP5</code> and <code>DT_WeaponMP5</code> to <code>CWeapon<YourWeaponName></code> and <code>DT_Weapon<YourWeaponName></code>.}}
{{Note|The gun uses stance and movement penalty. For zoom based effects try utilizing [[weapon_crossbow]] as a reference.}}
{{Note|Avoid using anything HL2MP related in terms of programming as much of the code for how weapon data is parsed is NOT compatible.}}


=== weapon_mp5.cpp ===
<source lang=cpp>
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//


== The Code ==
#include "cbase.h"
#include "basehlcombatweapon.h"
#include "npcevent.h"
#include "basecombatcharacter.h"
#include "ai_basenpc.h"
#include "player.h"
#include "game.h"
#include "in_buttons.h"
#include "ai_memory.h"
#include "soundent.h"
#include "rumble_shared.h"
#include "gamestats.h"


Before you start you have to create two new files. Call them "weapon_<yourweaponname>.cpp", place it both in the Client and Server side of your project.
// memdbgon must be the last include file in a .cpp file!!!
In this example I will use "weapon_ak47"
#include "tier0/memdbgon.h"


{{note|1='''Remember to change CWeaponAK47 to CWeapon<yourweaponname>'''}}
extern ConVar sk_plr_dmg_smg1_grenade;
{{note|1='''The gun features a small zoom function when you are using Secondary Fire!'''}}
{{note|1='''The gun has stance, health, zoom and recoil dependant accuracy. '''}}
{{note|1='''The gun can be set be semi-auto, burst or full auto. Not switchable from in-game.'''}}


//-----------------------------------------------------------------------------
// CWeaponMP5
//-----------------------------------------------------------------------------
class CWeaponMP5 : public CHLSelectFireMachineGun
{
DECLARE_DATADESC();


===WEAPON_AK47.CPP===
public:
DECLARE_CLASS( CWeaponMP5, CHLSelectFireMachineGun );
 
CWeaponMP5();
 
DECLARE_SERVERCLASS();
 
void Precache();
void AddViewKick();
void SecondaryAttack();
 
int GetMinBurst() { return 2; }
int GetMaxBurst() { return 5; }
 
virtual void Equip( CBaseCombatCharacter *pOwner );
bool Reload();
 
float GetFireRate() { return 0.075f; } // RPS, 60sec/800 rounds = 0.075
int CapabilitiesGet() { return bits_CAP_WEAPON_RANGE_ATTACK1; }
int WeaponRangeAttack2Condition( float flDot, float flDist );
Activity GetPrimaryAttackActivity();
 
// Values which allow our "spread" to change from button input from player
virtual const Vector &GetBulletSpread()
{
// Define "spread" parameters based on the "owner" and what they are doing
static Vector plrDuckCone = VECTOR_CONE_2DEGREES;
static Vector plrStandCone = VECTOR_CONE_3DEGREES;
static Vector plrMoveCone = VECTOR_CONE_4DEGREES;
static Vector npcCone = VECTOR_CONE_5DEGREES;
static Vector plrRunCone = VECTOR_CONE_6DEGREES;
static Vector plrJumpCone = VECTOR_CONE_9DEGREES;
 
if ( GetOwner() && GetOwner()->IsNPC() )
return npcCone;
 
//static Vector cone;
 
// We must know the player "owns" the weapon before different cones may be used
CBasePlayer *pPlayer = ToBasePlayer( GetOwnerEntity() );
if ( pPlayer->m_nButtons & IN_DUCK )
return plrDuckCone;
if ( pPlayer->m_nButtons & IN_FORWARD )
return plrMoveCone;
if ( pPlayer->m_nButtons & IN_BACK )
return plrMoveCone;
if ( pPlayer->m_nButtons & IN_MOVERIGHT )
return plrMoveCone;
if ( pPlayer->m_nButtons & IN_MOVELEFT )
return plrMoveCone;
if ( pPlayer->m_nButtons & IN_JUMP )
return plrJumpCone;
if ( pPlayer->m_nButtons & IN_SPEED )
return plrRunCone;
if ( pPlayer->m_nButtons & IN_RUN )
return plrRunCone;
else
return plrStandCone;
}
 
const WeaponProficiencyInfo_t *GetProficiencyValues();
 
void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir );
void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary );
void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
 
DECLARE_ACTTABLE();
};
 
IMPLEMENT_SERVERCLASS_ST( CWeaponMP5, DT_WeaponMP5 )
END_SEND_TABLE()
 
LINK_ENTITY_TO_CLASS( weapon_mp5, CWeaponMP5 );
PRECACHE_WEAPON_REGISTER( weapon_mp5 );
 
BEGIN_DATADESC( CWeaponMP5 )
END_DATADESC()
 
acttable_t CWeaponMP5::m_acttable[] =
{
{ ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true },
{ ACT_RELOAD, ACT_RELOAD_SMG1, true },
{ ACT_IDLE, ACT_IDLE_SMG1, true },
{ ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true },
 
{ ACT_WALK, ACT_WALK_RIFLE, true },
{ ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true  },
 
// Readiness activities (not aiming)
{ ACT_IDLE_RELAXED, ACT_IDLE_SMG1_RELAXED, false }, // Never aims
{ ACT_IDLE_STIMULATED, ACT_IDLE_SMG1_STIMULATED, false },
{ ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG1, false }, // Always aims
 
{ ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false }, // Never aims
{ ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false },
{ ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false }, // Always aims
 
{ ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false }, // Never aims
{ ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false },
{ ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false }, // Always aims
 
// Readiness activities (aiming)
{ ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false }, // Never aims
{ ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false },
{ ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false }, // Always aims
 
{ ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false }, // Never aims
{ ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false },
{ ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false }, // Always aims
 
{ ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false }, // Never aims
{ ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false },
{ ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false }, // Always aims
// End readiness activities
 
{ ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true },
{ ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true },
{ ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true },
{ ACT_RUN, ACT_RUN_RIFLE, true },
{ ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true },
{ ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true },
{ ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true },
{ ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG1, true },
{ ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG1_LOW, true },
{ ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false },
{ ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false },
{ ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false },
{ ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true },
};
 
IMPLEMENT_ACTTABLE( CWeaponMP5 );
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CWeaponMP5::CWeaponMP5()
{
m_fMinRange1 = 0; // No minimum range
m_fMaxRange1 = 1400;
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::Precache()
{
BaseClass::Precache();
}
 
//-----------------------------------------------------------------------------
// Purpose: Give this weapon longer range when wielded by an ally NPC.
//-----------------------------------------------------------------------------
void CWeaponMP5::Equip( CBaseCombatCharacter *pOwner )
{
if ( pOwner->Classify() == CLASS_PLAYER_ALLY )
m_fMaxRange1 = 3000;
else
m_fMaxRange1 = 1400;
 
BaseClass::Equip( pOwner );
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir )
{
// FIXME: Use the returned number of bullets to account for >10hz firerate
WeaponSoundRealtime( SINGLE_NPC );
 
CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() );
pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), 0 );
 
pOperator->DoMuzzleFlash();
m_iClip1--;
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary )
{
// Ensure we have enough rounds in the magazine
m_iClip1++;
 
Vector vecShootOrigin, vecShootDir;
QAngle angShootDir;
GetAttachment( LookupAttachment("muzzle"), vecShootOrigin, angShootDir );
AngleVectors( angShootDir, &vecShootDir );
FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir );
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
{
switch ( pEvent->event )
{
case EVENT_WEAPON_SMG1:
{
Vector vecShootOrigin, vecShootDir;
QAngle angDiscard;
 
// Support old style attachment point firing
if ( (pEvent->options == NULL) || (pEvent->options[0] == '\0') || (!pOperator->GetAttachment(pEvent->options, vecShootOrigin, angDiscard)) )
vecShootOrigin = pOperator->Weapon_ShootPosition();
 
CAI_BaseNPC *npc = pOperator->MyNPCPointer();
ASSERT( npc != NULL );
vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin );
 
FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir );
}
break;
 
default:
BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
break;
}
}
 
//-----------------------------------------------------------------------------
// Purpose:
// Output : Activity
//-----------------------------------------------------------------------------
Activity CWeaponMP5::GetPrimaryAttackActivity()
{
if ( m_nShotsFired < 2 )
return ACT_VM_PRIMARYATTACK;
 
if ( m_nShotsFired < 3 )
return ACT_VM_RECOIL1;
 
if ( m_nShotsFired < 4 )
return ACT_VM_RECOIL2;
 
return ACT_VM_RECOIL3;
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CWeaponMP5::Reload()
{
bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
if ( fRet )
WeaponSound( RELOAD );
 
return fRet;
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::AddViewKick()
{
#define EASY_DAMPEN 2.3f
#define MAX_VERTICAL_KICK 17.0f // Degrees
#define SLIDE_LIMIT 0.11f // Seconds
 
// Get the view kick
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
 
if ( pPlayer == NULL )
return;
 
DoMachineGunKick( pPlayer, EASY_DAMPEN, MAX_VERTICAL_KICK, m_fFireDuration, SLIDE_LIMIT );
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::SecondaryAttack()
{
}
 
//-----------------------------------------------------------------------------
// Purpose:
// Input  : flDot -
//          flDist -
// Output : int
//-----------------------------------------------------------------------------
int CWeaponMP5::WeaponRangeAttack2Condition( float flDot, float flDist )
{
return COND_NONE;
}
 
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const WeaponProficiencyInfo_t *CWeaponMP5::GetProficiencyValues()
{
static WeaponProficiencyInfo_t proficiencyTable[] =
{
{ 7.0, 0.75 },
{ 5.00, 0.75 },
{ 10.0/3.0, 0.75 },
{ 5.0/3.0, 0.75 },
{ 1.00, 1.0 },
};
 
COMPILE_TIME_ASSERT( ARRAYSIZE(proficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1);
 
return proficiencyTable;
}
</source>
 
=== c_weapon__stubs_hl2.cpp ===
 
This file is located on the Client side of the mod and allows our new weapon to be pulled by the game when necessary. In this example, we will be adding the following code below line 44:
<source lang=cpp>
STUB_WEAPON_CLASS( weapon_mp5, WeaponMP5, C_HLSelectFireMachineGun );
</source>
 
=== hl2_gamerules.cpp ===
 
We will need to add some data to this file to define the parameters for the '''skill.cfg''' to use later (in this case we are using '''weapon_mp5'''). There are two areas we are concerned with, so let's start below line 182 by adding the following:
 
<source lang=cpp>
ConVar sk_plr_dmg_mp5 ( "sk_plr_dmg_mp5","0", FCVAR_REPLICATED );
ConVar sk_npc_dmg_mp5 ( "sk_npc_dmg_mp5","0", FCVAR_REPLICATED);
ConVar sk_max_mp5 ( "sk_max_mp5","0", FCVAR_REPLICATED);
</source>
 
Next, if we want custom ammo types, we need to define the physics or properties of the ammo. Find line 1823 and add the following below:
 
{{Note| This is not necessary if no new ammo class is created.}}
 
<source lang=cpp>
def.AddAmmoType("Mp5",      DMG_BULLET, TRACER_LINE_AND_WHIZ, "sk_plr_dmg_mp5", "sk_npc_dmg_mp5", "sk_max_mp5", BULLET_IMPULSE(150, 1225), 0 );
</source>
 
=== item_ammo.cpp ===
 
In this step, we will declare the class of ammo used, and define the world model for the game to fetch. In this example, we add the code below line 590.
 
{{Note|This is not necessary if no new ammo class is created.}}
 
<source lang=cpp>
// ========================================================================
// >> Box 9mm MP5 Rounds
// ========================================================================
class CItem_BoxMP5Rounds : public CItem
{
public:
DECLARE_CLASS( CItem_BoxMP5Rounds, CItem );
 
void Spawn()
{
Precache();
SetModel( "models/items/boxsrounds.mdl" );
 
BaseClass::Spawn();
}
void Precache()
{
PrecacheModel( "models/items/boxsrounds.mdl" );
}
bool MyTouch( CBasePlayer *pPlayer )
{
if ( ITEM_GiveAmmo(pPlayer, SIZE_AMMO_MP5, "MP5") )
{
if ( g_pGameRules->ItemShouldRespawn(this) == GR_ITEM_RESPAWN_NO )
{
UTIL_Remove( this );
}
return true;
}
return false;
}
};
LINK_ENTITY_TO_CLASS( item_box_mp5rounds, CItem_BoxMP5Rounds );
LINK_ENTITY_TO_CLASS( item_ammo_mp5, CItem_BoxMP5Rounds );
 
// ========================================================================
// >> Large Box 9mm MP5 Rounds
// ========================================================================
class CItem_LargeBoxMP5Rounds : public CItem
{
public:
DECLARE_CLASS( CItem_LargeBoxMP5Rounds, CItem );
 
void Spawn()
{
Precache();
SetModel( "models/items/boxsrounds.mdl" );
 
BaseClass::Spawn();
}
void Precache()
{
PrecacheModel ( "models/items/boxsrounds.mdl" );
}
bool MyTouch( CBasePlayer *pPlayer )
{
if ( ITEM_GiveAmmo(pPlayer, SIZE_AMMO_MP5_LARGE, "MP5") )
{
if ( g_pGameRules->ItemShouldRespawn(this) == GR_ITEM_RESPAWN_NO )
{
UTIL_Remove( this );
}
return true;
}
return false;
}
};
LINK_ENTITY_TO_CLASS( item_large_box_mp5rounds, CItem_LargeBoxMP5Rounds );
LINK_ENTITY_TO_CLASS( item_ammo_mp5_large, CItem_LargeBoxMP5Rounds );
</source>
 
=== items.h ===
 
Half-Life 2 (including both EP1 and EP2) defines base quantities of ammunition to supply internally. The skill or difficulty of the game determines a percentage of the base quantity listed in '''items.h''' to supply the player. For example, where 'X' = the amount of ammo listed in '''items.h''': Easy = 1.25 * X, Normal = 1 * X, and Hard = .6 * X. Knowing this we can decide some values to use as a base. Let's start at line 36.
 
{{Note|This is not necessary if no new ammo class is created.}}


<source lang=cpp>
<source lang=cpp>
   
#define SIZE_AMMO_MP5 35
#define SIZE_AMMO_MP5_LARGE 110
</source>
 
=== weapon_mp5.txt ===
 
One of the final bits to assemble is the text file or script used by the game. The script defines the bucket position (weapon slot), ammunition, sound scripts, and HUD icons to be gathered for use in-game.
 
{{Note|IMPORTANT: References to items such as <code>Weapon_MP5.Single</code> are handled through '''game_sounds_weapons.txt''' and will need to be defined BEFORE any sounds can be gathered in-game!}}
 
<pre>
// 9x19mm Submachine Gun
 
WeaponData
{
    // Weapon data is loaded by both the Game and Client DLLs.
    "printname"            "MP5"
    "viewmodel"            "models/weapons/v_mp5.mdl"
    "playermodel"          "models/weapons/w_mp5.mdl"
    "anim_prefix"          "smg2"
    "bucket"                "2"
    "bucket_position"      "2"
    "bucket_360"            "1"
    "bucket_position_360"  "0"
 
    "clip_size"            "30"
    "default_clip"          "40"
 
    "clip2_size"            "-1"
    "default_clip2"        "-1"
 
    "primary_ammo"          "MP5"
    "secondary_ammo"        "None"
 
    "weight"                "3"
    "rumble"                "3"
    "item_flags"            "0"
 
    "BuiltRightHanded"      "0"
    "AllowFlipping"        "1"
 
    // Sounds for the weapon. There is a max of 16 sounds per category (i.e. max 16 "single_shot" sounds)
    SoundData
    {
        "reload"            "Weapon_MP5.Reload"
        "reload_npc"        "Weapon_MP5.NPC_Reload"
        "empty"            "Weapon_MP5.Empty"
        "single_shot"      "Weapon_MP5.Single"
        "single_shot_npc"  "Weapon_MP5.NPC_Single"
        "special1"          "Weapon_MP5.Special1"
        "special2"          "Weapon_MP5.Special2"
        "double_shot"      "Weapon_MP5.Double"
        "burst"            "Weapon_MP5.Burst"
    }
 
    // Weapon Sprite data is loaded by the Client DLL.
    TextureData
    {
        "weapon"
        {
                "font"      "WeaponIcons"
                "character" "a"
        }
        "weapon_s"
        {
                "font"      "WeaponIconsSelected"
                "character" "a"
        }
        "weapon_small"
        {
                "font"      "WeaponIconsSmall"
                "character" "a"
        }
        "ammo"
        {
                "font"      "WeaponIconsSmall"
                "character" "p"
        }
        "ammo2"
        {
                "font"      "WeaponIconsSmall"
                "character" "t"
        }
        "crosshair"
        {
                "font"      "Crosshairs"
                "character" "Q"
        }
        "autoaim"
        {
                "file"      "sprites/crosshairs"
                "x"        "0"
                "y"        "48"
                "width"    "24"
                "height"    "24"
        }
    }
}
</pre>
 
=== skill.cfg ===
 
This is the external script where our damage and ammo supply tables are established. This can be edited at any point to whatever values fit right in your mod. Here are the data tables we will need to add:
 
<pre>
sk_plr_dmg_mp5 "11"
sk_npc_dmg_mp5 "9"
sk_max_mp5 "180"
</pre>
 
=== Extra ===
 
If you want to make the weapon spawn with [[impulse|impulse 101]] you need to go to the '''player.cpp''' file and under <code>void CBasePlayer::CheatImpulseCommands( int iImpulse )</code> on line 6210 add the following:
 
<source lang=cpp>
GiveNamedItem( "weapon_mp5" );
</source>
 
== Tips ==
 
{{Note|To avoid creating new ammo types, try to use existing ammo classes such as <code>"SMG1"</code> or <code>"Pistol"</code> in your '''weapon_<yourweaponname>.txt''' file.}}
{{Note|The same method mentioned above can be used for sound references.}}
{{Note|The same method again can be used for both the v_ and w_ models.}}
{{Note|Scripts can be found in the "mod/scripts" directory including all sound and level scripting."}}
{{Note|It may be necessary to run an [[autoexec]] script to force the game to load '''skill.cfg''' (located in the "mod/cfg" directory).}}
 
== The Code (Multiplayer or HL2MP Version Only) ==
 
Before you start you have to create two new files. Call them '''weapon_<yourweaponname>.cpp''', place it both in the Client and Server side of your project.
In this example, I will use '''weapon_ak47.txt'''
 
{{Note|Remember to change <code>CWeaponAK47</code> and <code>DT_WeaponAK47</code> to <code>CWeapon<YourWeaponName></code> and <code>DT_Weapon<YourWeaponName></code>.}}
{{Note|The gun features a small zoom function when you are using Secondary Fire!}}
{{Note|The gun has stance, health, zoom and recoil dependant accuracy.}}
{{Note|The gun can be set be semi-auto, burst or full auto. Not switchable from in-game.}}
 
=== weapon_ak47.cpp ===
 
<source lang=cpp>
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
 
#include "cbase.h"
#include "cbase.h"
#include "npcevent.h"
#include "npcevent.h"
Line 27: Line 620:


#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
  #include "c_hl2mp_player.h"
#include "c_hl2mp_player.h"
#else
#else
  #include "hl2mp_player.h"
#include "hl2mp_player.h"
#endif
#endif
 
#include "weapon_hl2mpbasehlmpcombatweapon.h"
#include "weapon_hl2mpbasehlmpcombatweapon.h"
//modify this to alter the rate of fire
#define ROF 0.075f //RPS, 60 Sec / 800 Rounds = 0.075f


//The gun will fire up to this number of bullets while you hold the fire button.
// Modify this to alter the rate of fire
//If you set it to 1 the gun will be semi auto. If you set it to 3 the gun will fire three round bursts
#define ROF 0.075f // RPS, 60sec/800 rounds = 0.075
 
// The gun will fire up to this number of bullets while you hold the fire button
// If you set it to 1 the gun will be semi-auto. If you set it to 3 the gun will fire three-round bursts
#define BURST 500;
#define BURST 500;
 
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
#define CWeaponAK47 C_WeaponAK47
#define CWeaponAK47 C_WeaponAK47
#endif
#endif
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// CWeaponAK47
// CWeaponAK47
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class CWeaponAK47 : public CBaseHL2MPCombatWeapon
class CWeaponAK47 : public CBaseHL2MPCombatWeapon
{
{
  public:
public:
   
DECLARE_CLASS( CWeaponAK47, CBaseHL2MPCombatWeapon );
    DECLARE_CLASS( CWeaponAK47, CBaseHL2MPCombatWeapon );
 
    CWeaponAK47(void);
CWeaponAK47();
    DECLARE_NETWORKCLASS();
 
    DECLARE_PREDICTABLE();
DECLARE_NETWORKCLASS();
DECLARE_PREDICTABLE();
    void Precache( void );
 
    void ItemPostFrame( void );
void Precache();
    void ItemPreFrame( void );
void ItemPostFrame();
    void ItemBusyFrame( void );
void ItemPreFrame();
    void PrimaryAttack( void );
void ItemBusyFrame();
    void AddViewKick( void );
void PrimaryAttack();
    void DryFire( void );
void AddViewKick();
    void GetStance( void );
void DryFire();
    bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); // Required so that you un-zoom when switching weapons
void GetStance();
    Activity GetPrimaryAttackActivity( void );
bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); // Required so that you un-zoom when switching weapons
Activity GetPrimaryAttackActivity();
    virtual bool Reload( void );
 
virtual bool Reload();
    int GetMinBurst() { return 2; }
 
    int GetMaxBurst() { return 5; }
int GetMinBurst() { return 2; }
    float GetFireRate( void ) { return ROF; }
int GetMaxBurst() { return 5; }
float GetFireRate() { return ROF; }
    //modify this part to control the general accuracy of the gun
 
// Modify this part to control the general accuracy of the gun
virtual const Vector &GetBulletSpread()
{
Vector cone = VECTOR_CONE_1DEGREES;
 
// If you don't need stance and health dependent accuracy, you can just remove this
if ( m_iStance == E_DUCK )
cone = VECTOR_CONE_1DEGREES;
if ( m_iStance == E_STAND )
cone = VECTOR_CONE_2DEGREES;
if ( m_iStance == E_MOVE )
cone = VECTOR_CONE_3DEGREES;
if ( m_iStance == E_RUN )
cone = VECTOR_CONE_4DEGREES;
if ( m_iStance == E_INJURED )
cone = VECTOR_CONE_3DEGREES;
if ( m_iStance == E_JUMP )
cone = VECTOR_CONE_4DEGREES;
if ( m_iStance == E_DYING )
cone = VECTOR_CONE_10DEGREES;
 
// This part simulates recoil. Each successive shot will have increased spread
if ( m_iBurst != BURST )
{
for ( int i = m_iBurst; i < BURST; i++ )
{
cone += VECTOR_CONE_1DEGREES;
}
}
 
// This part is the zoom modifier. If zoomed in, lower the bullet spread
if ( m_bInZoom )
cone -= VECTOR_CONE_1DEGREES;
 
return cone;
}


    virtual const Vector& GetBulletSpread( void )
void ToggleZoom();
    {
void CheckZoomToggle();
      Vector cone = VECTOR_CONE_1DEGREES;
     
      // if you don't need stance and health dependent accuracy, you can just remove this. 
      if ( m_iStance == E_DUCK )
        { cone = VECTOR_CONE_1DEGREES;}
      if ( m_iStance == E_STAND )
        { cone = VECTOR_CONE_2DEGREES;}
      if ( m_iStance == E_MOVE )
        { cone = VECTOR_CONE_3DEGREES;}
      if ( m_iStance == E_RUN )
        { cone = VECTOR_CONE_4DEGREES;}
      if ( m_iStance == E_INJURED )
        { cone = VECTOR_CONE_3DEGREES;}
      if ( m_iStance == E_JUMP )
        { cone = VECTOR_CONE_4DEGREES;}
      if ( m_iStance == E_DYING )
        { cone = VECTOR_CONE_10DEGREES;}


      //This part simlates recoil. Each successive shot will have increased spread.
DECLARE_ACTTABLE();
      if (m_iBurst!=BURST)
      {
        for (int i = m_iBurst; i < BURST; i++)
        {
          cone += VECTOR_CONE_1DEGREES;
        }
      }
     
      //This part is the zoom modifier. If in zoom, lower the bullet spread.
      if (m_bInZoom)
      {
        cone -= VECTOR_CONE_1DEGREES;
      }


      return cone;
private:
    }
CNetworkVar( int, m_iBurst );
   
CNetworkVar( bool, m_bInZoom );
    void ToggleZoom( void );
CNetworkVar( float, m_flAttackEnds );
    void CheckZoomToggle( void );
CNetworkVar( int, m_iStance);
    DECLARE_ACTTABLE();


  private:
private:
    CNetworkVar( int, m_iBurst );
CWeaponAK47( const CWeaponAK47 & );
    CNetworkVar( bool, m_bInZoom );
    CNetworkVar( float, m_flAttackEnds );
    CNetworkVar( int, m_iStance);
  private:
    CWeaponAK47( const CWeaponAK47 & );
};
};
 
IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAK47, DT_WeaponAK47 )
IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAK47, DT_WeaponAK47 )
 
BEGIN_NETWORK_TABLE( CWeaponAK47, DT_WeaponAK47 )
BEGIN_NETWORK_TABLE( CWeaponAK47, DT_WeaponAK47 )
#ifdef CLIENT_DLL
#ifdef CLIENT_DLL
  RecvPropInt(  RECVINFO( m_iBurst) ),
RecvPropInt(  RECVINFO( m_iBurst) ),
  RecvPropBool( RECVINFO( m_bInZoom ) ),
RecvPropBool( RECVINFO( m_bInZoom ) ),
  RecvPropTime( RECVINFO( m_flAttackEnds ) ),
RecvPropTime( RECVINFO( m_flAttackEnds ) ),
  RecvPropInt(  RECVINFO( m_iStance ) ),
RecvPropInt(  RECVINFO( m_iStance ) ),
#else
#else
  SendPropInt(  SENDINFO( m_iBurst ) ),
SendPropInt(  SENDINFO( m_iBurst ) ),
  SendPropBool( SENDINFO( m_bInZoom ) ),
SendPropBool( SENDINFO( m_bInZoom ) ),
  SendPropTime( SENDINFO( m_flAttackEnds ) ),
SendPropTime( SENDINFO( m_flAttackEnds ) ),
  SendPropInt(  SENDINFO( m_iStance ) ),
SendPropInt(  SENDINFO( m_iStance ) ),
#endif
#endif
END_NETWORK_TABLE()
END_NETWORK_TABLE()
 
BEGIN_PREDICTION_DATA( CWeaponAK47 )
BEGIN_PREDICTION_DATA( CWeaponAK47 )
END_PREDICTION_DATA()
END_PREDICTION_DATA()
 
LINK_ENTITY_TO_CLASS( weapon_AK47, CWeaponAK47 );
LINK_ENTITY_TO_CLASS( weapon_AK47, CWeaponAK47 );
PRECACHE_WEAPON_REGISTER( weapon_AK47 );
PRECACHE_WEAPON_REGISTER( weapon_AK47 );
 
acttable_t CWeaponAK47::m_acttable[] =
acttable_t CWeaponAK47::m_acttable[] =
{
{
  { ACT_MP_STAND_IDLE, ACT_HL2MP_IDLE_AR2, false },
{ ACT_MP_STAND_IDLE, ACT_HL2MP_IDLE_AR2, false },
  { ACT_MP_CROUCH_IDLE, ACT_HL2MP_IDLE_CROUCH_AR2, false },
{ ACT_MP_CROUCH_IDLE, ACT_HL2MP_IDLE_CROUCH_AR2, false },
  { ACT_MP_RUN, ACT_HL2MP_RUN_AR2, false },
{ ACT_MP_RUN, ACT_HL2MP_RUN_AR2, false },
  { ACT_MP_CROUCHWALK, ACT_HL2MP_WALK_CROUCH_AR2, false },
{ ACT_MP_CROUCHWALK, ACT_HL2MP_WALK_CROUCH_AR2, false },
  { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
{ ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
  { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
{ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
  { ACT_MP_RELOAD_STAND, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
{ ACT_MP_RELOAD_STAND, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
  { ACT_MP_RELOAD_CROUCH, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
{ ACT_MP_RELOAD_CROUCH, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
  { ACT_MP_JUMP, ACT_HL2MP_JUMP_AR2, false },
{ ACT_MP_JUMP, ACT_HL2MP_JUMP_AR2, false },
};
};
 
IMPLEMENT_ACTTABLE( CWeaponAK47 );
IMPLEMENT_ACTTABLE( CWeaponAK47 );
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Constructor
// Purpose: Constructor
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CWeaponAK47::CWeaponAK47( void )
CWeaponAK47::CWeaponAK47()
{
{
  m_iBurst=BURST;
m_iBurst = BURST;
  m_iStance=10;
m_iStance = 10;
  m_fMinRange1 = 1;
m_fMinRange1 = 1;
  m_fMaxRange1 = 1500;
m_fMaxRange1 = 1500;
  m_fMinRange2 = 1;
m_fMinRange2 = 1;
  m_fMaxRange2 = 200;
m_fMaxRange2 = 200;
  m_bFiresUnderwater = false;
m_bFiresUnderwater = false;
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Required for caching the entity during loading
// Purpose: Required for caching the entity during loading
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::Precache( void )
void CWeaponAK47::Precache()
{
{
  BaseClass::Precache();
BaseClass::Precache();
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: The gun is empty, plays a clicking noise with a dryfire anim
// Purpose: The gun is empty, plays a clicking noise with a dryfire anim
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::DryFire( void )
void CWeaponAK47::DryFire()
{
{
  WeaponSound( EMPTY );
WeaponSound( EMPTY );
  SendWeaponAnim( ACT_VM_DRYFIRE );
SendWeaponAnim( ACT_VM_DRYFIRE );
  m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: This happens if you click and hold the primary fire button
// Purpose: This happens if you click and hold the primary fire button
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::PrimaryAttack( void )
void CWeaponAK47::PrimaryAttack()
{
{
  //do we have any bullets left from the current burst cycle?  
// Do we have any bullets left from the current burst cycle?
  if (m_iBurst!=0)  
if ( m_iBurst != 0 )
  {
{
    CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
    CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
if ( !pPlayer )
    if ( !pPlayer )
return;
      { return; }
   
    WeaponSound( SINGLE );
    pPlayer->DoMuzzleFlash();


    SendWeaponAnim( ACT_VM_PRIMARYATTACK );
WeaponSound( SINGLE );
    pPlayer->SetAnimation( PLAYER_ATTACK1 );
pPlayer->DoMuzzleFlash();
    ToHL2MPPlayer(pPlayer)->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
   
    // Each time the player fires the gun, reset the view punch. 
    if ( pOwner )
      { pOwner->ViewPunchReset(); }


    BaseClass::PrimaryAttack();
SendWeaponAnim( ACT_VM_PRIMARYATTACK );
pPlayer->SetAnimation( PLAYER_ATTACK1 );
    // We fired one shot, decrease the number of bullets available for this burst cycle  
ToHL2MPPlayer(pPlayer)->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );
    m_iBurst--;
 
    m_flNextPrimaryAttack =gpGlobals->curtime + ROF;
// Each time the player fires the gun, reset the view punch
    m_flAttackEnds = gpGlobals->curtime + SequenceDuration();
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  }
if ( pOwner )
pOwner->ViewPunchReset();
 
BaseClass::PrimaryAttack();
 
// We fired one shot, decrease the number of bullets available for this burst cycle
m_iBurst--;
m_flNextPrimaryAttack = gpGlobals->curtime + ROF;
m_flAttackEnds = gpGlobals->curtime + SequenceDuration();
}
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemPreFrame( void )
void CWeaponAK47::ItemPreFrame()
{
{
  GetStance();
GetStance();
  BaseClass::ItemPreFrame();
 
BaseClass::ItemPreFrame();
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose:
// Purpose:  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemBusyFrame( void )
void CWeaponAK47::ItemBusyFrame()
{
{
  // Allow zoom toggling even when we're reloading
// Allow zoom toggling even when we're reloading
  CheckZoomToggle();
CheckZoomToggle();
  BaseClass::ItemBusyFrame();
 
BaseClass::ItemBusyFrame();
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Allows firing as fast as button is pressed
// Purpose: Allows firing as fast as the button is pressed
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemPostFrame( void )
void CWeaponAK47::ItemPostFrame()
{
{
  BaseClass::ItemPostFrame();
BaseClass::ItemPostFrame();
  if ( m_bInReload )
 
    { return; }
if ( m_bInReload )
return;
  CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
 
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  if ( pOwner == NULL )
if ( pOwner == NULL )
    { return; }
return;
 
 
  if ( pOwner->m_nButtons & IN_ATTACK )
if ( pOwner->m_nButtons & IN_ATTACK )
  {
{
    if (m_flAttackEnds<gpGlobals->curtime)
if ( m_flAttackEnds < gpGlobals->curtime )
      { SendWeaponAnim(ACT_VM_IDLE); }
SendWeaponAnim( ACT_VM_IDLE );
  }
}
  else
else
  {
{
    //The firing cycle ended. Reset the burst counter to the max value
// The firing cycle ended. Reset the burst counter to the max value
    m_iBurst=BURST;
m_iBurst = BURST;
    if ( ( pOwner->m_nButtons & IN_ATTACK ) && ( m_flNextPrimaryAttack < gpGlobals->curtime ) && ( m_iClip1 <= 0 ) )
if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack < gpGlobals->curtime) && (m_iClip1 <= 0) )
      { DryFire(); }
DryFire();
  }
}
  CheckZoomToggle();
 
  //check the character's current stance for the accuracy calculation
CheckZoomToggle();
  GetStance();
 
// Check the character's current stance for the accuracy calculation
GetStance();
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: If we have bullets left then play the attack anim otherwise idle
// Purpose: If we have bullets left then play the attack anim otherwise idle
// Output : int
// Output : int
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
Activity CWeaponAK47::GetPrimaryAttackActivity( void )
Activity CWeaponAK47::GetPrimaryAttackActivity()
{
{
  if (m_iBurst!=0)
if ( m_iBurst != 0 )
    { return ACT_VM_PRIMARYATTACK; }
return ACT_VM_PRIMARYATTACK;
  else
else
    { return ACT_VM_IDLE; }
return ACT_VM_IDLE;
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: The gun is being reloaded  
// Purpose: The gun is being reloaded  
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CWeaponAK47::Reload( void )
bool CWeaponAK47::Reload()
{
{
  bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
  if ( fRet )
if ( fRet )
  {
{
    WeaponSound( RELOAD );
WeaponSound( RELOAD );
    ToHL2MPPlayer(GetOwner())->DoAnimationEvent( PLAYERANIMEVENT_RELOAD );
ToHL2MPPlayer(GetOwner())->DoAnimationEvent( PLAYERANIMEVENT_RELOAD );
    //reset the burst counter to the default
 
    m_iBurst=BURST;
// Reset the burst counter to the default
  }
m_iBurst = BURST;
  return fRet;
}
return fRet;
}
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Put away the gun and disable zoom if needed
// Purpose: Put away the gun and disable zoom if needed
//-----------------------------------------------------------------------------  
//-----------------------------------------------------------------------------
bool CWeaponAK47::Holster(CBaseCombatWeapon *pSwitchingTo /* = NULL */)
bool CWeaponAK47::Holster( CBaseCombatWeapon *pSwitchingTo /*= NULL*/ )
{
{
  if ( m_bInZoom )
if ( m_bInZoom )
    { ToggleZoom(); }
ToggleZoom();
  return BaseClass::Holster( pSwitchingTo );
 
return BaseClass::Holster( pSwitchingTo );
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Calculate the viewkick
// Purpose: Calculate the view kick
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::AddViewKick( void )
void CWeaponAK47::AddViewKick()
{
{
  CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
  if ( pPlayer == NULL )
if ( pPlayer == NULL )
    { return; }
return;
 
  int iSeed = CBaseEntity::GetPredictionRandomSeed() & 255;
int iSeed = CBaseEntity::GetPredictionRandomSeed() & 255;
  RandomSeed( iSeed );
RandomSeed( iSeed );
 
  QAngle viewPunch;
QAngle viewPunch;
 
  viewPunch.x = random->RandomFloat( 0.25f, 0.5f );
viewPunch.x = random->RandomFloat( 0.25f, 0.5f );
  viewPunch.y = random->RandomFloat( -.6f, .6f );
viewPunch.y = random->RandomFloat( -.6f, .6f );
  viewPunch.z = 0.0f;
viewPunch.z = 0.0f;
 
  //Add it to the view punch
// Add it to the view punch
  pPlayer->ViewPunch( viewPunch );
pPlayer->ViewPunch( viewPunch );
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Toggle the zoom by changing the client's FOV
// Purpose: Toggle the zoom by changing the client's FOV
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::ToggleZoom( void )
void CWeaponAK47::ToggleZoom()
{
{
  CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
if ( pPlayer == NULL )
  if ( pPlayer == NULL )
return;
    { return; }
 
 
#ifndef CLIENT_DLL
  #ifndef CLIENT_DLL
if ( m_bInZoom )
    if ( m_bInZoom )
{
    {
// Narrowing the field of view here is what gives us the zoomed effect
      // Narrowing the Field Of View here is what gives us the zoomed effect
if ( pPlayer->SetFOV(this, 0, 0.2f) )
      if ( pPlayer->SetFOV( this, 0, 0.2f ) )
m_bInZoom = false;
      {
}
        m_bInZoom = false;
else
{
        // Send a message to hide the scope
if ( pPlayer->SetFOV(this, 45, 0.1f) )
        /* CSingleUserRecipientFilter filter(pPlayer);
m_bInZoom = true;
        UserMessageBegin(filter, "ShowScope");
}
        WRITE_BYTE(0);
#endif
        MessageEnd();*/
      }
    }
    else
    {
      if ( pPlayer->SetFOV( this, 45, 0.1f ) )
      {
        m_bInZoom = true;
        // Send a message to Show the scope
        /* CSingleUserRecipientFilter filter(pPlayer);
        UserMessageBegin(filter, "ShowScope");
        WRITE_BYTE(1);
        MessageEnd();*/
      }
    }
  #endif
}
}
 
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: Toggle the zoom if the Sec attack button was pressed
// Purpose: Toggle the zoom if the Sec attack button was pressed
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponAK47::CheckZoomToggle( void )
void CWeaponAK47::CheckZoomToggle()
{
{
  CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
  if ( pPlayer && (pPlayer->m_afButtonPressed & IN_ATTACK2))
if ( pPlayer && (pPlayer->m_afButtonPressed & IN_ATTACK2) )
    { ToggleZoom(); }
ToggleZoom();
}
}


Line 397: Line 975:
// Purpose: Get the current stance/status of the player
// Purpose: Get the current stance/status of the player
//-----------------------------------------------------------------------------  
//-----------------------------------------------------------------------------  
void CWeaponAK47::GetStance( void )
void CWeaponAK47::GetStance()
{
{
  CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
  if ( pPlayer == NULL )
if ( pPlayer == NULL )
    { return; }
return;
 
  m_iStance= E_STAND;


  // movement based stance
m_iStance = E_STAND;
  if ( pPlayer->m_nButtons & IN_DUCK)   
    { m_iStance= E_DUCK;}
  if ( pPlayer->m_nButtons & IN_FORWARD) 
    { m_iStance= E_MOVE;}
  if ( pPlayer->m_nButtons & IN_BACK)   
    { m_iStance= E_MOVE;}
  if ( pPlayer->m_nButtons & IN_MOVERIGHT)
    { m_iStance= E_MOVE;}
  if ( pPlayer->m_nButtons & IN_MOVELEFT)
    { m_iStance= E_MOVE;}
  if ( pPlayer->m_nButtons & IN_RUN)     
    { m_iStance= E_RUN;}
  if ( pPlayer->m_nButtons & IN_SPEED)   
    { m_iStance= E_RUN;}
  if ( pPlayer->m_nButtons & IN_JUMP)   
    { m_iStance= E_JUMP;}


  //health based status
// Movement based stance
  if ( pPlayer->GetHealth()<25)          
if ( pPlayer->m_nButtons & IN_DUCK )
    { m_iStance= E_INJURED;}
m_iStance = E_DUCK;
  if ( pPlayer->GetHealth()<10)          
if ( pPlayer->m_nButtons & IN_FORWARD )
    { m_iStance= E_DYING;}
m_iStance = E_MOVE;
if ( pPlayer->m_nButtons & IN_BACK )
m_iStance = E_MOVE;
if ( pPlayer->m_nButtons & IN_MOVERIGHT )
m_iStance = E_MOVE;
if ( pPlayer->m_nButtons & IN_MOVELEFT )
m_iStance = E_MOVE;
if ( pPlayer->m_nButtons & IN_RUN )
m_iStance = E_RUN;
if ( pPlayer->m_nButtons & IN_SPEED )
m_iStance = E_RUN;
if ( pPlayer->m_nButtons & IN_JUMP )
m_iStance = E_JUMP;
 
// Health based status
if ( pPlayer->GetHealth() < 25 )
m_iStance = E_INJURED;
if ( pPlayer->GetHealth() < 10 )
m_iStance = E_DYING;
}
}
</source>
</source>


===WEAPON_HL2MPBASEHLMPCOMBATWEAPON.h===
=== weapon_hl2mpbasehlmpcombatweapon.h ===


{{warning| For Source 2013 Users, Please ignore this step, or you will receive errors during compiling!}}
Here you will declare the <code>m_iStance</code>'s.
 
Add this below <code>CBaseHL2MPCombatWeapon( const CBaseHL2MPCombatWeapon & );</code> around line 60:
Here you will declare the m_istance's.
Add this below CBaseHL2MPCombatWeapon( const CBaseHL2MPCombatWeapon & ); - around line 60:


<source lang=cpp>
<source lang=cpp>
enum stances
enum stances
{
{
  E_STAND = 0,
E_STAND = 0,
  E_DUCK = 1,
E_DUCK = 1,
  E_MOVE = 2,
E_MOVE = 2,
  E_RUN = 3,
E_RUN = 3,
  E_INJURED = 4,
E_INJURED = 4,
  E_JUMP = 5,
E_JUMP = 5,
  E_DYING = 6,
E_DYING = 6,
};
};
</source>
</source>


===HL2MP_GAMERULES.CPP===
=== hl2mp_gamerules.cpp ===


Here we need to declare the ammo type of our new weapon, as well as max ammo, etc...
Here we need to declare the ammo type of our new weapon, as well as max ammo, etc...
Add this below def.AddAmmoType("AR2"     -=- around line 931:
Add this below <code>def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, BULLET_IMPULSE(200, 1225), 0 );</code> around line 927:


<source lang=cpp>
<source lang=cpp>
Line 461: Line 1,036:
</source>
</source>


Change "Rifle" to the ammotype you have added in your weapon_yourweaponname.txt in your mod/script folder, or you could use an existing ammotype like the SMG1 ammotype rather than creating a new ammotype. If so, then you can skip this step, as the existing ammotype already is implemented in this code.
Change <code>"Rifle"</code> to the ammo type you have added in your '''weapon_<yourweaponname>.txt''' in your mod/script folder, or you could use an existing ammo type like <code>"SMG1"</code> rather than creating a new one.
 
If so, then you can skip this step, as the existing ammotype already is implemented in this code.
 
And if you want to change max ammo then change 500 to another value.
And if you want to change max ammo then change 500 to another value.


===C_WEAPON__STUBS_HL2.CPP===
=== c_weapon__stubs_hl2.cpp ===


In order to use the weapon at all you need to declare your new weapon here.  
In order to use the weapon at all, you need to declare your new weapon here.
Below STUB_WEAPON_CLASS( weapon_ar2, WeaponAR2, C_HLMachineGun ); add this:
Below <code>STUB_WEAPON_CLASS( weapon_ar2, WeaponAR2, C_HLMachineGun );</code> add this:


<source lang=cpp>
<source lang=cpp>
Line 473: Line 1,051:
</source>
</source>


=== weapon_ak47.txt ===


===SCRIPT FILE===
Now you have to tell your weapon which ammotype, model, weight, flags, damage, anim prefix, and what bucket in the weapon selection HUD it will use.
To do this you need to create a script file in your '''mod/scripts''' folder. Call it '''weapon_<yourweaponname>.txt''' in this case I called mine '''weapon_ak47.txt'''.
Here is an example:


Now you have to tell your weapon which ammotype, model, weight, flags, damage, anim prefix and what bucket in the weapon selection HUD it will use.
<pre>
To do this you need to create a script file in your "mod/scripts" folder. Call it "weapon_<yourweaponname>" in this case I called mine "weapon_ak47"
// 7.62x39mm Assault Rifle
Here is an example:


<source lang=cpp>
WeaponData
WeaponData
{
{
// Weapon data is loaded by both the Game and Client DLLs.
    // Weapon data is loaded by both the Game and Client DLLs.
"printname" "AK47"
    "printname"             "AK-47"
"viewmodel" "models/weapons/v_rif_ak47.mdl" // Client view model
    "viewmodel"             "models/weapons/v_rif_ak47.mdl"
"playermodel" "models/weapons/w_rif_ak47.mdl" // Server world model
    "playermodel"           "models/weapons/w_rif_ak47.mdl"
"anim_prefix" "ar2" // The HUD icon which will be shown when browsing for this weapon in the weapon selection hud, most likely loaded from halflife2.ttf or hl2mp.ttf.
    "anim_prefix"           "ar2"
"bucket" "5" // The horizontal direction.
    "bucket"               "5"
"bucket_position" "3" // The vertical direction.
    "bucket_position"       "3"
 
    "clip_size"            "30"
    "default_clip"          "500"


"clip_size" "30" // How many bullets you can fire before you have to reload.
    "clip2_size"           "-1"
"default_clip" "500" // How much ammo this weapon can carry.
    "default_clip2"         "-1"


"clip2_size" "-1"
    "primary_ammo"         "Rifle"
"default_clip2" "-1"
    "secondary_ammo"       "None"


"primary_ammo" "Rifle" // The ammotype for the weapon, this can be found in hl2mp_gamerules.cpp: def.AddAmmoType(
    "weight"               "6"
"secondary_ammo" "None"
    "item_flags"           "0"
    "damage"                "75"


"weight" "6" // If the weight is higher than your current weapon you will automatically switch to that weapon when you pick it up.
    "BuiltRightHanded"     "1"
"item_flags" "0"
    "AllowFlipping"         "1"
        "BuiltRightHanded"             "1"
        "AllowFlipping"                 "1"
        "damage"                        "75" // Damage per bullet, if it is a higher value then you will more likely die by one bullet.


// Sounds for the weapon. There is a max of 16 sounds per category (i.e. max 16 "single_shot" sounds)
    // Sounds for the weapon. There is a max of 16 sounds per category (i.e. max 16 "single_shot" sounds)
SoundData
    SoundData
{
    {
"special1" "Weapon_CombineGuard.Special1"
        "special1"         "Weapon_CombineGuard.Special1"
"empty" "Weapon_IRifle.Empty"
        "empty"             "Weapon_IRifle.Empty"
"double_shot" "Weapon_AK47.Single"
        "double_shot"       "Weapon_AK47.Single"
"reload" "Weapon_AK47.Reload" // Sound triggered when reloading. Activate this in the game_sounds_weapons.
        "reload"           "Weapon_AK47.Reload"
"single_shot" "Weapon_AK47.Single" // Sound triggered when shooting.  -=-
        "single_shot"       "Weapon_AK47.Single"


// NPC SECTION
        // NPC section
"single_shot_npc" "Weapon_AK47.NPC_Single"
        "single_shot_npc"   "Weapon_AK47.NPC_Single"
"reload_npc" "Weapon_AR2.NPC_Reload"
        "reload_npc"       "Weapon_AR2.NPC_Reload"
"double_shot_npc" "Weapon_AK47.NPC_Double"
        "double_shot_npc"   "Weapon_AK47.NPC_Double"
}
    }


// Weapon Sprite data is loaded by the Client DLL.
    // Weapon Sprite data is loaded by the Client DLL.
TextureData
    TextureData
{
    {
"weapon"
        "weapon"
{
        {
"font" "WeaponIcons"
                "font"     "WeaponIcons"
"character" "l"
                "character" "l"
}
        }
"weapon_s"
        "weapon_s"
{
        {
"font" "WeaponIconsSelected"
                "font"     "WeaponIconsSelected"
"character" "l"
                "character" "l"
}
        }
"weapon_small"
        "weapon_small"
{
        {
"font" "WeaponIconsSmall"
                "font"     "WeaponIconsSmall"
"character" "l"
                "character" "l"
}
        }
"ammo"
        "ammo"
{
        {
"font" "WeaponIconsSmall"
                "font"     "WeaponIconsSmall"
"character" "u"
                "character" "u"
}
        }
"ammo2"
        "ammo2"
{
        {
"font" "WeaponIconsSmall"
                "font"     "WeaponIconsSmall"
"character" "z"
                "character" "z"
}
        }
"crosshair"
        "crosshair"
{
        {
"font" "Crosshairs"
                "font"     "Crosshairs"
"character" "Q"
                "character" "Q"
}
        }
"autoaim"
        "autoaim"
{
        {
"file" "sprites/crosshairs"
                "file"     "sprites/crosshairs"
"x" "0"
                "x"         "0"
"y" "48"
                "y"         "48"
"width" "24"
                "width"     "24"
"height" "24"
                "height"   "24"
}
        }
}
    }
}
}
</source>
</pre>


=== Extra ===


===EXTRA===
You can now use the weapon in-game. Compile your mod and type this in the console:


You can now use the weapon in-game. Compile your mod and type this in console:
<pre>
 
<source lang=cpp>
sv_cheats 1
sv_cheats 1
give weapon_<yourweaponname>
give weapon_<yourweaponname>
</source>
</pre>
You should now be wielding your new weapon.
You should now be wielding your new weapon.


If you want to spawn with your new weapon add this in '''HL2MP_PLAYER.CPP''' below void CHL2MP_Player::GiveDefaultItems( void ):
If you want to spawn with your new weapon add this in '''hl2mp_player.cpp''' below <code>void CHL2MP_Player::GiveDefaultItems( void )</code>:


<source lang=cpp>
<source lang=cpp>
Line 583: Line 1,162:
</source>
</source>


And if you want to get your new weapon by the impulse101 cheat command then add this in the same file but below void CHL2MP_Player::GiveAllItems( void ):
And if you want to get your new weapon by the [[impulse|impulse 101]] cheat command then add this in the same file but below <code>void CHL2MP_Player::GiveAllItems( void )</code>:


<source lang=cpp>
<source lang=cpp>
Line 589: Line 1,168:
</source>
</source>


And you probably want to create default maps for your mod and add this weapon as a pickup, then add this into the .FGD file for your mod:
And you probably want to create default maps for your mod and add this weapon as a pickup, then add this into the [[FGD|.fgd]] file for your mod:


<source lang=cpp>
<source lang=cpp>
Line 595: Line 1,174:
</source>
</source>


To change the accuracy of your weapon then edit this part of the "WEAPON_YOURWEAPONNAME.CPP" file:
To change the accuracy of your weapon then edit this part of the '''weapon_<yourweaponname>.cpp''' file:


<source lang=cpp>
<source lang=cpp>
static Vector cone=VECTOR_CONE_1DEGREES;
static Vector cone = VECTOR_CONE_1DEGREES;
if (m_bInZoom)
if ( m_bInZoom )
{
{
  if (m_iStance==E_DUCK)  
if ( m_iStance == E_DUCK )
    { cone = VECTOR_CONE_0DEGREES;}
cone = VECTOR_CONE_0DEGREES;
  if (m_iStance==E_STAND)
if ( m_iStance == E_STAND )
    { cone = VECTOR_CONE_1DEGREES;}
cone = VECTOR_CONE_1DEGREES;
  if (m_iStance==E_MOVE)  
if ( m_iStance == E_MOVE )
    { cone = VECTOR_CONE_1DEGREES;}
cone = VECTOR_CONE_1DEGREES;
  if (m_iStance==E_RUN)  
if ( m_iStance == E_RUN )
    { cone = VECTOR_CONE_3DEGREES;}
cone = VECTOR_CONE_3DEGREES;
  if (m_iStance==E_INJURED)  
if ( m_iStance == E_INJURED )
    { cone = VECTOR_CONE_4DEGREES;}
cone = VECTOR_CONE_4DEGREES;
  if (m_iStance==E_JUMP)  
if ( m_iStance == E_JUMP )
    { cone = VECTOR_CONE_5DEGREES;}
cone = VECTOR_CONE_5DEGREES;
  if (m_iStance==E_DYING)  
if ( m_iStance == E_DYING )
    { cone = VECTOR_CONE_10DEGREES;}
cone = VECTOR_CONE_10DEGREES;
}
}
if (!m_bInZoom)
else
{
{
  if (m_iStance==E_DUCK)  
if ( m_iStance == E_DUCK )
    { cone = VECTOR_CONE_1DEGREES;}
cone = VECTOR_CONE_1DEGREES;
  if (m_iStance==E_STAND)  
if ( m_iStance == E_STAND )
    { cone = VECTOR_CONE_1DEGREES;}
cone = VECTOR_CONE_1DEGREES;
  if (m_iStance==E_MOVE)  
if ( m_iStance == E_MOVE )
    { cone = VECTOR_CONE_2DEGREES;}
cone = VECTOR_CONE_2DEGREES;
  if (m_iStance==E_RUN)  
if ( m_iStance == E_RUN )
    { cone = VECTOR_CONE_3DEGREES;}
cone = VECTOR_CONE_3DEGREES;
  if (m_iStance==E_INJURED)  
if ( m_iStance == E_INJURED )
    { cone = VECTOR_CONE_4DEGREES;}
cone = VECTOR_CONE_4DEGREES;
  if (m_iStance==E_JUMP)  
if ( m_iStance == E_JUMP )
    { cone = VECTOR_CONE_5DEGREES;}
cone = VECTOR_CONE_5DEGREES;
  if (m_iStance==E_DYING)  
if ( m_iStance == E_DYING )
    { cone = VECTOR_CONE_10DEGREES;}
cone = VECTOR_CONE_10DEGREES;
}
}
int bs=BURST;
 
if (m_iBurst!=bs)
int iBurst = BURST;
if ( m_iBurst != iBurst )
{
{
  //for (int i=0;i<(bs-m_iBurst);i++)
for ( int i = m_iBurst; i < iBurst; i++ )
  for (int i = m_iBurst; i < bs; i++)
{
  {  
cone += VECTOR_CONE_1DEGREES;
    cone=cone+VECTOR_CONE_1DEGREES;
}
  }
}
}
</source>
</source>


A higher value means more imprecise accuracy, and bullets will more likely spread in all angles.
A higher value means more imprecise accuracy, and bullets will more likely spread in all angles.
A lower value is recommended.  
A lower value is recommended.


To change the rate of fire edit this part of the "WEAPON_YOURWEAPONNAME.CPP" file:
To change the rate of fire edit this part of the '''weapon_<yourweaponname>.cpp''' file:


<source lang=cpp>
<source lang=cpp>
//modify this to alter the rate of fire
// Modify this to alter the rate of fire
#define ROF 0.080f //800 rounds/min
#define ROF 0.075f // 800 rounds/min
</source>
</source>


To add different animations to your weapon (not default ones, but other animations implemented by Valve) edit this part of the "WEAPON_YOURWEAPONNAME.CPP" file:
To add different animations to your weapon (not default ones, but other animations implemented by Valve) edit this part of the '''weapon_<yourweaponname>.cpp''' file:


<source lang=cpp>
<source lang=cpp>
acttable_t CWeaponAK47::m_acttable[] =
acttable_t CWeaponAK47::m_acttable[] =
{
{
  { ACT_MP_STAND_IDLE, ACT_HL2MP_IDLE_AR2, false },
{ ACT_MP_STAND_IDLE, ACT_HL2MP_IDLE_AR2, false },
  { ACT_MP_CROUCH_IDLE, ACT_HL2MP_IDLE_CROUCH_AR2, false },
{ ACT_MP_CROUCH_IDLE, ACT_HL2MP_IDLE_CROUCH_AR2, false },
  { ACT_MP_RUN, ACT_HL2MP_RUN_AR2, false },
{ ACT_MP_RUN, ACT_HL2MP_RUN_AR2, false },
  { ACT_MP_CROUCHWALK, ACT_HL2MP_WALK_CROUCH_AR2, false },
{ ACT_MP_CROUCHWALK, ACT_HL2MP_WALK_CROUCH_AR2, false },
  { ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
{ ACT_MP_ATTACK_STAND_PRIMARYFIRE, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
  { ACT_MP_ATTACK_CROUCH_PRIMARYFIRE,ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
{ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false },
  { ACT_MP_RELOAD_STAND, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
{ ACT_MP_RELOAD_STAND, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
  { ACT_MP_RELOAD_CROUCH, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
{ ACT_MP_RELOAD_CROUCH, ACT_HL2MP_GESTURE_RELOAD_AR2, false },
  { ACT_MP_JUMP, ACT_HL2MP_JUMP_AR2, false },
{ ACT_MP_JUMP, ACT_HL2MP_JUMP_AR2, false },
};
};
 
IMPLEMENT_ACTTABLE( CWeaponAK47 );
IMPLEMENT_ACTTABLE( CWeaponAK47 );
</source>
</source>


If you would rather have the SMG1 animations then change _AR2 to _SMG1 instead.
If you would rather have the SMG1 animations then change <code>_AR2</code> to <code>_SMG1</code> instead.
And if you want to use the pistol animations change _AR2 to _PISTOL... etc...
And if you want to use the pistol animations change <code>_AR2</code> to <code>_PISTOL</code> etc...


==Finish==
== Finish ==


You have now successfully implemented a new weapon into your mod, I hope that this was understandable and easy to follow!
You have now successfully implemented a new weapon into your mod, I hope that this was understandable and easy to follow!


Disclaimer:  
'''Disclaimer:''' This piece of code was originally created by [[User:Pendra|Pendra]] for the mod 'X-Com - Last Hope 2' ([https://www.moddb.com/mods/x-com-last-hope ModDB])
This piece of code was originally created by Pendra for the mod X-Com - Last Hope 2


[[Category:Weapons programming]] {{DISPLAYTITLE:Adding a new weapon for your mod}}
[[Category:Weapons programming]]
[[Category:Tutorials]]
[[Category:Tutorials]]

Latest revision as of 04:02, 12 July 2024

English (en)Translate (Translate)


This article will deal with creating a custom weapon for your mod. This was based on an existing article which recently was removed:

Note.pngNote:IMPORTANT! If you want to achieve this for singleplayer, please see the "Single Player Only" example(s). Multiplayer ActTable does not include NPC animations and other events!
Note.pngNote:The HL2MP portions have only been tested with the latest HL2MP OB engine code.
Note.pngNote:This tutorial covers code implementation and scripting ONLY. For sound and model creation, check out modeling and sound creation.

The Code (Single Player Only)

Before you start you will need to either create a new blank .cpp or copy an existing entity (such as weapon_smg1) to use as a template. Using an existing entity makes for easy copy/paste work but it is risky as it is easy to miss definitions or calls to the original weapon entity. Also, be sure to back up any core HL2 files if you are using them as a template BEFORE saving any changes. In either case, your new weapon .cpp file should read weapon_<yourweaponname>.cpp, place it on the Server side of your project. In this example, we will use weapon_mp5 throughout the SP part of the tutorial.

Note.pngNote:Remember to change CWeaponMP5 and DT_WeaponMP5 to CWeapon<YourWeaponName> and DT_Weapon<YourWeaponName>.
Note.pngNote:The gun uses stance and movement penalty. For zoom based effects try utilizing weapon_crossbow as a reference.
Note.pngNote:Avoid using anything HL2MP related in terms of programming as much of the code for how weapon data is parsed is NOT compatible.

weapon_mp5.cpp

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "basehlcombatweapon.h"
#include "npcevent.h"
#include "basecombatcharacter.h"
#include "ai_basenpc.h"
#include "player.h"
#include "game.h"
#include "in_buttons.h"
#include "ai_memory.h"
#include "soundent.h"
#include "rumble_shared.h"
#include "gamestats.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern ConVar sk_plr_dmg_smg1_grenade;

//-----------------------------------------------------------------------------
// CWeaponMP5
//-----------------------------------------------------------------------------
class CWeaponMP5 : public CHLSelectFireMachineGun
{
	DECLARE_DATADESC();

public:
	DECLARE_CLASS( CWeaponMP5, CHLSelectFireMachineGun );

	CWeaponMP5();

	DECLARE_SERVERCLASS();

	void			Precache();
	void			AddViewKick();
	void			SecondaryAttack();

	int				GetMinBurst() { return 2; }
	int				GetMaxBurst() { return 5; }

	virtual void	Equip( CBaseCombatCharacter *pOwner );
	bool			Reload();

	float			GetFireRate() { return 0.075f; } // RPS, 60sec/800 rounds = 0.075
	int				CapabilitiesGet() { return bits_CAP_WEAPON_RANGE_ATTACK1; }
	int				WeaponRangeAttack2Condition( float flDot, float flDist );
	Activity		GetPrimaryAttackActivity();

	// Values which allow our "spread" to change from button input from player
	virtual const Vector &GetBulletSpread()
	{
		// Define "spread" parameters based on the "owner" and what they are doing
		static Vector plrDuckCone = VECTOR_CONE_2DEGREES;
		static Vector plrStandCone = VECTOR_CONE_3DEGREES;
		static Vector plrMoveCone = VECTOR_CONE_4DEGREES;
		static Vector npcCone = VECTOR_CONE_5DEGREES;
		static Vector plrRunCone = VECTOR_CONE_6DEGREES;
		static Vector plrJumpCone = VECTOR_CONE_9DEGREES;

		if ( GetOwner() && GetOwner()->IsNPC() )
			return npcCone;

		//static Vector cone;

		// We must know the player "owns" the weapon before different cones may be used
		CBasePlayer *pPlayer = ToBasePlayer( GetOwnerEntity() );
		if ( pPlayer->m_nButtons & IN_DUCK )
			return plrDuckCone;
		if ( pPlayer->m_nButtons & IN_FORWARD )
			return plrMoveCone;
		if ( pPlayer->m_nButtons & IN_BACK )
			return plrMoveCone;
		if ( pPlayer->m_nButtons & IN_MOVERIGHT )
			return plrMoveCone;
		if ( pPlayer->m_nButtons & IN_MOVELEFT )
			return plrMoveCone;
		if ( pPlayer->m_nButtons & IN_JUMP )
			return plrJumpCone;
		if ( pPlayer->m_nButtons & IN_SPEED )
			return plrRunCone;
		if ( pPlayer->m_nButtons & IN_RUN )
			return plrRunCone;
		else
			return plrStandCone;
	}

	const WeaponProficiencyInfo_t *GetProficiencyValues();

	void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir );
	void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary );
	void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );

	DECLARE_ACTTABLE();
};

IMPLEMENT_SERVERCLASS_ST( CWeaponMP5, DT_WeaponMP5 )
END_SEND_TABLE()

LINK_ENTITY_TO_CLASS( weapon_mp5, CWeaponMP5 );
PRECACHE_WEAPON_REGISTER( weapon_mp5 );

BEGIN_DATADESC( CWeaponMP5 )
END_DATADESC()

acttable_t CWeaponMP5::m_acttable[] =
{
	{ ACT_RANGE_ATTACK1,			ACT_RANGE_ATTACK_SMG1,			true },
	{ ACT_RELOAD,					ACT_RELOAD_SMG1,				true },
	{ ACT_IDLE,						ACT_IDLE_SMG1,					true },
	{ ACT_IDLE_ANGRY,				ACT_IDLE_ANGRY_SMG1,			true },

	{ ACT_WALK,						ACT_WALK_RIFLE,					true },
	{ ACT_WALK_AIM,					ACT_WALK_AIM_RIFLE,				true  },

	// Readiness activities (not aiming)
	{ ACT_IDLE_RELAXED,				ACT_IDLE_SMG1_RELAXED,			false }, // Never aims
	{ ACT_IDLE_STIMULATED,			ACT_IDLE_SMG1_STIMULATED,		false },
	{ ACT_IDLE_AGITATED,			ACT_IDLE_ANGRY_SMG1,			false }, // Always aims

	{ ACT_WALK_RELAXED,				ACT_WALK_RIFLE_RELAXED,			false }, // Never aims
	{ ACT_WALK_STIMULATED,			ACT_WALK_RIFLE_STIMULATED,		false },
	{ ACT_WALK_AGITATED,			ACT_WALK_AIM_RIFLE,				false }, // Always aims

	{ ACT_RUN_RELAXED,				ACT_RUN_RIFLE_RELAXED,			false }, // Never aims
	{ ACT_RUN_STIMULATED,			ACT_RUN_RIFLE_STIMULATED,		false },
	{ ACT_RUN_AGITATED,				ACT_RUN_AIM_RIFLE,				false }, // Always aims

	// Readiness activities (aiming)
	{ ACT_IDLE_AIM_RELAXED,			ACT_IDLE_SMG1_RELAXED,			false }, // Never aims
	{ ACT_IDLE_AIM_STIMULATED,		ACT_IDLE_AIM_RIFLE_STIMULATED,	false },
	{ ACT_IDLE_AIM_AGITATED,		ACT_IDLE_ANGRY_SMG1,			false }, // Always aims

	{ ACT_WALK_AIM_RELAXED,			ACT_WALK_RIFLE_RELAXED,			false }, // Never aims
	{ ACT_WALK_AIM_STIMULATED,		ACT_WALK_AIM_RIFLE_STIMULATED,	false },
	{ ACT_WALK_AIM_AGITATED,		ACT_WALK_AIM_RIFLE,				false }, // Always aims

	{ ACT_RUN_AIM_RELAXED,			ACT_RUN_RIFLE_RELAXED,			false }, // Never aims
	{ ACT_RUN_AIM_STIMULATED,		ACT_RUN_AIM_RIFLE_STIMULATED,	false },
	{ ACT_RUN_AIM_AGITATED,			ACT_RUN_AIM_RIFLE,				false }, // Always aims
	// End readiness activities

	{ ACT_WALK_AIM,					ACT_WALK_AIM_RIFLE,				true },
	{ ACT_WALK_CROUCH,				ACT_WALK_CROUCH_RIFLE,			true },
	{ ACT_WALK_CROUCH_AIM,			ACT_WALK_CROUCH_AIM_RIFLE,		true },
	{ ACT_RUN,						ACT_RUN_RIFLE,					true },
	{ ACT_RUN_AIM,					ACT_RUN_AIM_RIFLE,				true },
	{ ACT_RUN_CROUCH,				ACT_RUN_CROUCH_RIFLE,			true },
	{ ACT_RUN_CROUCH_AIM,			ACT_RUN_CROUCH_AIM_RIFLE,		true },
	{ ACT_GESTURE_RANGE_ATTACK1,	ACT_GESTURE_RANGE_ATTACK_SMG1,	true },
	{ ACT_RANGE_ATTACK1_LOW,		ACT_RANGE_ATTACK_SMG1_LOW,		true },
	{ ACT_COVER_LOW,				ACT_COVER_SMG1_LOW,				false },
	{ ACT_RANGE_AIM_LOW,			ACT_RANGE_AIM_SMG1_LOW,			false },
	{ ACT_RELOAD_LOW,				ACT_RELOAD_SMG1_LOW,			false },
	{ ACT_GESTURE_RELOAD,			ACT_GESTURE_RELOAD_SMG1,		true },
};

IMPLEMENT_ACTTABLE( CWeaponMP5 );

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CWeaponMP5::CWeaponMP5()
{
	m_fMinRange1 = 0; // No minimum range
	m_fMaxRange1 = 1400;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMP5::Precache()
{
	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose: Give this weapon longer range when wielded by an ally NPC.
//-----------------------------------------------------------------------------
void CWeaponMP5::Equip( CBaseCombatCharacter *pOwner )
{
	if ( pOwner->Classify() == CLASS_PLAYER_ALLY )
		m_fMaxRange1 = 3000;
	else
		m_fMaxRange1 = 1400;

	BaseClass::Equip( pOwner );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMP5::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir )
{
	// FIXME: Use the returned number of bullets to account for >10hz firerate
	WeaponSoundRealtime( SINGLE_NPC );

	CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() );
	pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), 0 );

	pOperator->DoMuzzleFlash();
	m_iClip1--;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMP5::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary )
{
	// Ensure we have enough rounds in the magazine
	m_iClip1++;

	Vector vecShootOrigin, vecShootDir;
	QAngle angShootDir;
	GetAttachment( LookupAttachment("muzzle"), vecShootOrigin, angShootDir );
	AngleVectors( angShootDir, &vecShootDir );
	FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMP5::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
{
	switch ( pEvent->event )
	{
	case EVENT_WEAPON_SMG1:
		{
			Vector vecShootOrigin, vecShootDir;
			QAngle angDiscard;

			// Support old style attachment point firing
			if ( (pEvent->options == NULL) || (pEvent->options[0] == '\0') || (!pOperator->GetAttachment(pEvent->options, vecShootOrigin, angDiscard)) )
				vecShootOrigin = pOperator->Weapon_ShootPosition();

			CAI_BaseNPC *npc = pOperator->MyNPCPointer();
			ASSERT( npc != NULL );
			vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin );

			FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir );
		}
		break;

	default:
		BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
		break;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Activity
//-----------------------------------------------------------------------------
Activity CWeaponMP5::GetPrimaryAttackActivity()
{
	if ( m_nShotsFired < 2 )
		return ACT_VM_PRIMARYATTACK;

	if ( m_nShotsFired < 3 )
		return ACT_VM_RECOIL1;

	if ( m_nShotsFired < 4 )
		return ACT_VM_RECOIL2;

	return ACT_VM_RECOIL3;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponMP5::Reload()
{
	bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
	if ( fRet )
		WeaponSound( RELOAD );

	return fRet;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMP5::AddViewKick()
{
	#define EASY_DAMPEN			2.3f
	#define MAX_VERTICAL_KICK	17.0f // Degrees
	#define SLIDE_LIMIT			0.11f // Seconds

	// Get the view kick
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	if ( pPlayer == NULL )
		return;

	DoMachineGunKick( pPlayer, EASY_DAMPEN, MAX_VERTICAL_KICK, m_fFireDuration, SLIDE_LIMIT );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponMP5::SecondaryAttack()
{
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : flDot -
//          flDist -
// Output : int
//-----------------------------------------------------------------------------
int CWeaponMP5::WeaponRangeAttack2Condition( float flDot, float flDist )
{
	return COND_NONE;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
const WeaponProficiencyInfo_t *CWeaponMP5::GetProficiencyValues()
{
	static WeaponProficiencyInfo_t proficiencyTable[] =
	{
		{ 7.0,		0.75	},
		{ 5.00,		0.75	},
		{ 10.0/3.0, 0.75	},
		{ 5.0/3.0,	0.75	},
		{ 1.00,		1.0		},
	};

	COMPILE_TIME_ASSERT( ARRAYSIZE(proficiencyTable) == WEAPON_PROFICIENCY_PERFECT + 1);

	return proficiencyTable;
}

c_weapon__stubs_hl2.cpp

This file is located on the Client side of the mod and allows our new weapon to be pulled by the game when necessary. In this example, we will be adding the following code below line 44:

STUB_WEAPON_CLASS( weapon_mp5, WeaponMP5, C_HLSelectFireMachineGun );

hl2_gamerules.cpp

We will need to add some data to this file to define the parameters for the skill.cfg to use later (in this case we are using weapon_mp5). There are two areas we are concerned with, so let's start below line 182 by adding the following:

ConVar	sk_plr_dmg_mp5			( "sk_plr_dmg_mp5","0", FCVAR_REPLICATED );
ConVar	sk_npc_dmg_mp5			( "sk_npc_dmg_mp5","0", FCVAR_REPLICATED);
ConVar	sk_max_mp5				( "sk_max_mp5","0", FCVAR_REPLICATED);

Next, if we want custom ammo types, we need to define the physics or properties of the ammo. Find line 1823 and add the following below:

Note.pngNote: This is not necessary if no new ammo class is created.
def.AddAmmoType("Mp5",       DMG_BULLET,	TRACER_LINE_AND_WHIZ,	 "sk_plr_dmg_mp5",	"sk_npc_dmg_mp5",	"sk_max_mp5",	BULLET_IMPULSE(150, 1225), 0 );

item_ammo.cpp

In this step, we will declare the class of ammo used, and define the world model for the game to fetch. In this example, we add the code below line 590.

Note.pngNote:This is not necessary if no new ammo class is created.
// ========================================================================
//	>> Box 9mm MP5 Rounds
// ========================================================================
class CItem_BoxMP5Rounds : public CItem
{
public:
	DECLARE_CLASS( CItem_BoxMP5Rounds, CItem );

	void Spawn()
	{
		Precache();
		SetModel( "models/items/boxsrounds.mdl" );

		BaseClass::Spawn();
	}
	void Precache()
	{
		PrecacheModel( "models/items/boxsrounds.mdl" );
	}
	bool MyTouch( CBasePlayer *pPlayer )
	{
		if ( ITEM_GiveAmmo(pPlayer, SIZE_AMMO_MP5, "MP5") )
		{
			if ( g_pGameRules->ItemShouldRespawn(this) == GR_ITEM_RESPAWN_NO )
			{
				UTIL_Remove( this );
			}
			return true;
		}
		return false;
	}
};
LINK_ENTITY_TO_CLASS( item_box_mp5rounds, CItem_BoxMP5Rounds );
LINK_ENTITY_TO_CLASS( item_ammo_mp5, CItem_BoxMP5Rounds );

// ========================================================================
//	>> Large Box 9mm MP5 Rounds
// ========================================================================
class CItem_LargeBoxMP5Rounds : public CItem
{
public:
	DECLARE_CLASS( CItem_LargeBoxMP5Rounds, CItem );

	void Spawn()
	{
		Precache();
		SetModel( "models/items/boxsrounds.mdl" );

		BaseClass::Spawn();
	}
	void Precache()
	{
		PrecacheModel ( "models/items/boxsrounds.mdl" );
	}
	bool MyTouch( CBasePlayer *pPlayer )
	{
		if ( ITEM_GiveAmmo(pPlayer, SIZE_AMMO_MP5_LARGE, "MP5") )
		{
			if ( g_pGameRules->ItemShouldRespawn(this) == GR_ITEM_RESPAWN_NO )
			{
				UTIL_Remove( this );
			}
			return true;
		}
		return false;
	}
};
LINK_ENTITY_TO_CLASS( item_large_box_mp5rounds, CItem_LargeBoxMP5Rounds );
LINK_ENTITY_TO_CLASS( item_ammo_mp5_large, CItem_LargeBoxMP5Rounds );

items.h

Half-Life 2 (including both EP1 and EP2) defines base quantities of ammunition to supply internally. The skill or difficulty of the game determines a percentage of the base quantity listed in items.h to supply the player. For example, where 'X' = the amount of ammo listed in items.h: Easy = 1.25 * X, Normal = 1 * X, and Hard = .6 * X. Knowing this we can decide some values to use as a base. Let's start at line 36.

Note.pngNote:This is not necessary if no new ammo class is created.
#define SIZE_AMMO_MP5				35
#define SIZE_AMMO_MP5_LARGE			110

weapon_mp5.txt

One of the final bits to assemble is the text file or script used by the game. The script defines the bucket position (weapon slot), ammunition, sound scripts, and HUD icons to be gathered for use in-game.

Note.pngNote:IMPORTANT: References to items such as Weapon_MP5.Single are handled through game_sounds_weapons.txt and will need to be defined BEFORE any sounds can be gathered in-game!
// 9x19mm Submachine Gun

WeaponData
{
    // Weapon data is loaded by both the Game and Client DLLs.
    "printname"             "MP5"
    "viewmodel"             "models/weapons/v_mp5.mdl"
    "playermodel"           "models/weapons/w_mp5.mdl"
    "anim_prefix"           "smg2"
    "bucket"                "2"
    "bucket_position"       "2"
    "bucket_360"            "1"
    "bucket_position_360"   "0"

    "clip_size"             "30"
    "default_clip"          "40"

    "clip2_size"            "-1"
    "default_clip2"         "-1"

    "primary_ammo"          "MP5"
    "secondary_ammo"        "None"

    "weight"                "3"
    "rumble"                "3"
    "item_flags"            "0"

    "BuiltRightHanded"      "0"
    "AllowFlipping"         "1"

    // Sounds for the weapon. There is a max of 16 sounds per category (i.e. max 16 "single_shot" sounds)
    SoundData
    {
        "reload"            "Weapon_MP5.Reload"
        "reload_npc"        "Weapon_MP5.NPC_Reload"
        "empty"             "Weapon_MP5.Empty"
        "single_shot"       "Weapon_MP5.Single"
        "single_shot_npc"   "Weapon_MP5.NPC_Single"
        "special1"          "Weapon_MP5.Special1"
        "special2"          "Weapon_MP5.Special2"
        "double_shot"       "Weapon_MP5.Double"
        "burst"             "Weapon_MP5.Burst"
    }

    // Weapon Sprite data is loaded by the Client DLL.
    TextureData
    {
        "weapon"
        {
                "font"      "WeaponIcons"
                "character" "a"
        }
        "weapon_s"
        {
                "font"      "WeaponIconsSelected"
                "character" "a"
        }
        "weapon_small"
        {
                "font"      "WeaponIconsSmall"
                "character" "a"
        }
        "ammo"
        {
                "font"      "WeaponIconsSmall"
                "character" "p"
        }
        "ammo2"
        {
                "font"      "WeaponIconsSmall"
                "character" "t"
        }
        "crosshair"
        {
                "font"      "Crosshairs"
                "character" "Q"
        }
        "autoaim"
        {
                "file"      "sprites/crosshairs"
                "x"         "0"
                "y"         "48"
                "width"     "24"
                "height"    "24"
        }
    }
}

skill.cfg

This is the external script where our damage and ammo supply tables are established. This can be edited at any point to whatever values fit right in your mod. Here are the data tables we will need to add:

sk_plr_dmg_mp5		"11"
sk_npc_dmg_mp5		"9"
sk_max_mp5		"180"

Extra

If you want to make the weapon spawn with impulse 101 you need to go to the player.cpp file and under void CBasePlayer::CheatImpulseCommands( int iImpulse ) on line 6210 add the following:

GiveNamedItem( "weapon_mp5" );

Tips

Note.pngNote:To avoid creating new ammo types, try to use existing ammo classes such as "SMG1" or "Pistol" in your weapon_<yourweaponname>.txt file.
Note.pngNote:The same method mentioned above can be used for sound references.
Note.pngNote:The same method again can be used for both the v_ and w_ models.
Note.pngNote:Scripts can be found in the "mod/scripts" directory including all sound and level scripting."
Note.pngNote:It may be necessary to run an autoexec script to force the game to load skill.cfg (located in the "mod/cfg" directory).

The Code (Multiplayer or HL2MP Version Only)

Before you start you have to create two new files. Call them weapon_<yourweaponname>.cpp, place it both in the Client and Server side of your project. In this example, I will use weapon_ak47.txt

Note.pngNote:Remember to change CWeaponAK47 and DT_WeaponAK47 to CWeapon<YourWeaponName> and DT_Weapon<YourWeaponName>.
Note.pngNote:The gun features a small zoom function when you are using Secondary Fire!
Note.pngNote:The gun has stance, health, zoom and recoil dependant accuracy.
Note.pngNote:The gun can be set be semi-auto, burst or full auto. Not switchable from in-game.

weapon_ak47.cpp

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "npcevent.h"
#include "in_buttons.h"

#ifdef CLIENT_DLL
	#include "c_hl2mp_player.h"
#else
	#include "hl2mp_player.h"
#endif

#include "weapon_hl2mpbasehlmpcombatweapon.h"

// Modify this to alter the rate of fire
#define ROF 0.075f // RPS, 60sec/800 rounds = 0.075

// The gun will fire up to this number of bullets while you hold the fire button
// If you set it to 1 the gun will be semi-auto. If you set it to 3 the gun will fire three-round bursts
#define BURST 500;

#ifdef CLIENT_DLL
	#define CWeaponAK47 C_WeaponAK47
#endif

//-----------------------------------------------------------------------------
// CWeaponAK47
//-----------------------------------------------------------------------------
class CWeaponAK47 : public CBaseHL2MPCombatWeapon
{
public:
	DECLARE_CLASS( CWeaponAK47, CBaseHL2MPCombatWeapon );

	CWeaponAK47();

	DECLARE_NETWORKCLASS();
	DECLARE_PREDICTABLE();

	void			Precache();
	void			ItemPostFrame();
	void			ItemPreFrame();
	void			ItemBusyFrame();
	void			PrimaryAttack();
	void			AddViewKick();
	void			DryFire();
	void			GetStance();
	bool			Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); // Required so that you un-zoom when switching weapons
	Activity		GetPrimaryAttackActivity();

	virtual bool	Reload();

	int				GetMinBurst() { return 2; }
	int				GetMaxBurst() { return 5; }
	float			GetFireRate() { return ROF; }

	// Modify this part to control the general accuracy of the gun
	virtual const Vector &GetBulletSpread()
	{
		Vector cone = VECTOR_CONE_1DEGREES;

		// If you don't need stance and health dependent accuracy, you can just remove this
		if ( m_iStance == E_DUCK )
			cone = VECTOR_CONE_1DEGREES;
		if ( m_iStance == E_STAND )
			cone = VECTOR_CONE_2DEGREES;
		if ( m_iStance == E_MOVE )
			cone = VECTOR_CONE_3DEGREES;
		if ( m_iStance == E_RUN )
			cone = VECTOR_CONE_4DEGREES;
		if ( m_iStance == E_INJURED )
			cone = VECTOR_CONE_3DEGREES;
		if ( m_iStance == E_JUMP )
			cone = VECTOR_CONE_4DEGREES;
		if ( m_iStance == E_DYING )
			cone = VECTOR_CONE_10DEGREES;

		// This part simulates recoil. Each successive shot will have increased spread
		if ( m_iBurst != BURST )
		{
			for ( int i = m_iBurst; i < BURST; i++ )
			{
				cone += VECTOR_CONE_1DEGREES;
			}
		}

		// This part is the zoom modifier. If zoomed in, lower the bullet spread
		if ( m_bInZoom )
			cone -= VECTOR_CONE_1DEGREES;

		return cone;
	}

	void ToggleZoom();
	void CheckZoomToggle();

	DECLARE_ACTTABLE();

private:
	CNetworkVar( int, m_iBurst );
	CNetworkVar( bool, m_bInZoom );
	CNetworkVar( float, m_flAttackEnds );
	CNetworkVar( int, m_iStance);

private:
	CWeaponAK47( const CWeaponAK47 & );
};

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAK47, DT_WeaponAK47 )

BEGIN_NETWORK_TABLE( CWeaponAK47, DT_WeaponAK47 )
#ifdef CLIENT_DLL
	RecvPropInt(  RECVINFO( m_iBurst) ),
	RecvPropBool( RECVINFO( m_bInZoom ) ),
	RecvPropTime( RECVINFO( m_flAttackEnds ) ),
	RecvPropInt(  RECVINFO( m_iStance ) ),
#else
	SendPropInt(  SENDINFO( m_iBurst ) ),
	SendPropBool( SENDINFO( m_bInZoom ) ),
	SendPropTime( SENDINFO( m_flAttackEnds ) ),
	SendPropInt(  SENDINFO( m_iStance ) ),
#endif
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CWeaponAK47 )
END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( weapon_AK47, CWeaponAK47 );
PRECACHE_WEAPON_REGISTER( weapon_AK47 );

acttable_t CWeaponAK47::m_acttable[] =
{
	{ ACT_MP_STAND_IDLE,					ACT_HL2MP_IDLE_AR2,						false },
	{ ACT_MP_CROUCH_IDLE,					ACT_HL2MP_IDLE_CROUCH_AR2,				false },
	{ ACT_MP_RUN,							ACT_HL2MP_RUN_AR2,						false },
	{ ACT_MP_CROUCHWALK,					ACT_HL2MP_WALK_CROUCH_AR2,				false },
	{ ACT_MP_ATTACK_STAND_PRIMARYFIRE,		ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2,		false },
	{ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE,		ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2,		false },
	{ ACT_MP_RELOAD_STAND,					ACT_HL2MP_GESTURE_RELOAD_AR2,			false },
	{ ACT_MP_RELOAD_CROUCH,					ACT_HL2MP_GESTURE_RELOAD_AR2,			false },
	{ ACT_MP_JUMP, 							ACT_HL2MP_JUMP_AR2,						false },
};

IMPLEMENT_ACTTABLE( CWeaponAK47 );

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CWeaponAK47::CWeaponAK47()
{
	m_iBurst			= BURST;
	m_iStance			= 10;
	m_fMinRange1		= 1;
	m_fMaxRange1		= 1500;
	m_fMinRange2		= 1;
	m_fMaxRange2		= 200;
	m_bFiresUnderwater	= false;
}

//-----------------------------------------------------------------------------
// Purpose: Required for caching the entity during loading
//-----------------------------------------------------------------------------
void CWeaponAK47::Precache()
{
	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose: The gun is empty, plays a clicking noise with a dryfire anim
//-----------------------------------------------------------------------------
void CWeaponAK47::DryFire()
{
	WeaponSound( EMPTY );
	SendWeaponAnim( ACT_VM_DRYFIRE );
	m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
}

//-----------------------------------------------------------------------------
// Purpose: This happens if you click and hold the primary fire button
//-----------------------------------------------------------------------------
void CWeaponAK47::PrimaryAttack()
{
	// Do we have any bullets left from the current burst cycle?
	if ( m_iBurst != 0 )
	{
		CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
		if ( !pPlayer )
			return;

		WeaponSound( SINGLE );
		pPlayer->DoMuzzleFlash();

		SendWeaponAnim( ACT_VM_PRIMARYATTACK );
		pPlayer->SetAnimation( PLAYER_ATTACK1 );
		ToHL2MPPlayer(pPlayer)->DoAnimationEvent( PLAYERANIMEVENT_ATTACK_PRIMARY );

		// Each time the player fires the gun, reset the view punch
		CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
		if ( pOwner )
			pOwner->ViewPunchReset();

		BaseClass::PrimaryAttack();

		// We fired one shot, decrease the number of bullets available for this burst cycle
		m_iBurst--;
		m_flNextPrimaryAttack = gpGlobals->curtime + ROF;
		m_flAttackEnds = gpGlobals->curtime + SequenceDuration();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemPreFrame()
{
	GetStance();

	BaseClass::ItemPreFrame();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemBusyFrame()
{
	// Allow zoom toggling even when we're reloading
	CheckZoomToggle();

	BaseClass::ItemBusyFrame();
}

//-----------------------------------------------------------------------------
// Purpose: Allows firing as fast as the button is pressed
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemPostFrame()
{
	BaseClass::ItemPostFrame();

	if ( m_bInReload )
		return;

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
	if ( pOwner == NULL )
		return;

	if ( pOwner->m_nButtons & IN_ATTACK )
	{
		if ( m_flAttackEnds < gpGlobals->curtime )
			SendWeaponAnim( ACT_VM_IDLE );
	}
	else
	{
		// The firing cycle ended. Reset the burst counter to the max value
		m_iBurst = BURST;
		if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack < gpGlobals->curtime) && (m_iClip1 <= 0) )
			DryFire();
	}

	CheckZoomToggle();

	// Check the character's current stance for the accuracy calculation
	GetStance();
}

//-----------------------------------------------------------------------------
// Purpose: If we have bullets left then play the attack anim otherwise idle
// Output : int
//-----------------------------------------------------------------------------
Activity CWeaponAK47::GetPrimaryAttackActivity()
{
	if ( m_iBurst != 0 )
		return ACT_VM_PRIMARYATTACK;
	else
		return ACT_VM_IDLE;
}

//-----------------------------------------------------------------------------
// Purpose: The gun is being reloaded 
//-----------------------------------------------------------------------------
bool CWeaponAK47::Reload()
{
	bool fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
	if ( fRet )
	{
		WeaponSound( RELOAD );
		ToHL2MPPlayer(GetOwner())->DoAnimationEvent( PLAYERANIMEVENT_RELOAD );

		// Reset the burst counter to the default
		m_iBurst = BURST;
	}
	return fRet;
}

//-----------------------------------------------------------------------------
// Purpose: Put away the gun and disable zoom if needed
//-----------------------------------------------------------------------------
bool CWeaponAK47::Holster( CBaseCombatWeapon *pSwitchingTo /*= NULL*/ )
{
	if ( m_bInZoom )
		ToggleZoom();

	return BaseClass::Holster( pSwitchingTo );
}

//-----------------------------------------------------------------------------
// Purpose: Calculate the view kick
//-----------------------------------------------------------------------------
void CWeaponAK47::AddViewKick()
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( pPlayer == NULL )
		return;

	int iSeed = CBaseEntity::GetPredictionRandomSeed() & 255;
	RandomSeed( iSeed );

	QAngle viewPunch;

	viewPunch.x = random->RandomFloat( 0.25f, 0.5f );
	viewPunch.y = random->RandomFloat( -.6f, .6f );
	viewPunch.z = 0.0f;

	// Add it to the view punch
	pPlayer->ViewPunch( viewPunch );
}

//-----------------------------------------------------------------------------
// Purpose: Toggle the zoom by changing the client's FOV
//-----------------------------------------------------------------------------
void CWeaponAK47::ToggleZoom()
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( pPlayer == NULL )
		return;

#ifndef CLIENT_DLL
	if ( m_bInZoom )
	{
		// Narrowing the field of view here is what gives us the zoomed effect
		if ( pPlayer->SetFOV(this, 0, 0.2f) )
			m_bInZoom = false;
	}
	else
	{
		if ( pPlayer->SetFOV(this, 45, 0.1f) )
			m_bInZoom = true;
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Toggle the zoom if the Sec attack button was pressed
//-----------------------------------------------------------------------------
void CWeaponAK47::CheckZoomToggle()
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( pPlayer && (pPlayer->m_afButtonPressed & IN_ATTACK2) )
		ToggleZoom();
}

//-----------------------------------------------------------------------------
// Purpose: Get the current stance/status of the player
//----------------------------------------------------------------------------- 
void CWeaponAK47::GetStance()
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( pPlayer == NULL )
		return;

	m_iStance = E_STAND;

	// Movement based stance
	if ( pPlayer->m_nButtons & IN_DUCK )
		m_iStance = E_DUCK;
	if ( pPlayer->m_nButtons & IN_FORWARD )
		m_iStance = E_MOVE;
	if ( pPlayer->m_nButtons & IN_BACK )
		m_iStance = E_MOVE;
	if ( pPlayer->m_nButtons & IN_MOVERIGHT )
		m_iStance = E_MOVE;
	if ( pPlayer->m_nButtons & IN_MOVELEFT )
		m_iStance = E_MOVE;
	if ( pPlayer->m_nButtons & IN_RUN )
		m_iStance = E_RUN;
	if ( pPlayer->m_nButtons & IN_SPEED )
		m_iStance = E_RUN;
	if ( pPlayer->m_nButtons & IN_JUMP )
		m_iStance = E_JUMP;

	// Health based status
	if ( pPlayer->GetHealth() < 25 )
		m_iStance = E_INJURED;
	if ( pPlayer->GetHealth() < 10 )
		m_iStance = E_DYING;
}

weapon_hl2mpbasehlmpcombatweapon.h

Here you will declare the m_iStance's. Add this below CBaseHL2MPCombatWeapon( const CBaseHL2MPCombatWeapon & ); around line 60:

enum stances
{
	E_STAND		= 0,
	E_DUCK		= 1,
	E_MOVE		= 2,
	E_RUN		= 3,
	E_INJURED	= 4,
	E_JUMP		= 5,
	E_DYING		= 6,
};

hl2mp_gamerules.cpp

Here we need to declare the ammo type of our new weapon, as well as max ammo, etc... Add this below def.AddAmmoType("AR2", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 60, BULLET_IMPULSE(200, 1225), 0 ); around line 927:

def.AddAmmoType("Rifle", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 500, BULLET_IMPULSE(200, 1225), 0 );

Change "Rifle" to the ammo type you have added in your weapon_<yourweaponname>.txt in your mod/script folder, or you could use an existing ammo type like "SMG1" rather than creating a new one.

If so, then you can skip this step, as the existing ammotype already is implemented in this code.

And if you want to change max ammo then change 500 to another value.

c_weapon__stubs_hl2.cpp

In order to use the weapon at all, you need to declare your new weapon here. Below STUB_WEAPON_CLASS( weapon_ar2, WeaponAR2, C_HLMachineGun ); add this:

STUB_WEAPON_CLASS( weapon_ak47, WeaponAK47, C_BaseHLCombatWeapon );

weapon_ak47.txt

Now you have to tell your weapon which ammotype, model, weight, flags, damage, anim prefix, and what bucket in the weapon selection HUD it will use. To do this you need to create a script file in your mod/scripts folder. Call it weapon_<yourweaponname>.txt in this case I called mine weapon_ak47.txt. Here is an example:

// 7.62x39mm Assault Rifle

WeaponData
{
    // Weapon data is loaded by both the Game and Client DLLs.
    "printname"             "AK-47"
    "viewmodel"             "models/weapons/v_rif_ak47.mdl"
    "playermodel"           "models/weapons/w_rif_ak47.mdl"
    "anim_prefix"           "ar2"
    "bucket"                "5"
    "bucket_position"       "3"

    "clip_size"             "30"
    "default_clip"          "500"

    "clip2_size"            "-1"
    "default_clip2"         "-1"

    "primary_ammo"          "Rifle"
    "secondary_ammo"        "None"

    "weight"                "6"
    "item_flags"            "0"
    "damage"                "75"

    "BuiltRightHanded"      "1"
    "AllowFlipping"         "1"

    // Sounds for the weapon. There is a max of 16 sounds per category (i.e. max 16 "single_shot" sounds)
    SoundData
    {
        "special1"          "Weapon_CombineGuard.Special1"
        "empty"             "Weapon_IRifle.Empty"
        "double_shot"       "Weapon_AK47.Single"
        "reload"            "Weapon_AK47.Reload"
        "single_shot"       "Weapon_AK47.Single"

        // NPC section
        "single_shot_npc"   "Weapon_AK47.NPC_Single"
        "reload_npc"        "Weapon_AR2.NPC_Reload"
        "double_shot_npc"   "Weapon_AK47.NPC_Double"
    }

    // Weapon Sprite data is loaded by the Client DLL.
    TextureData
    {
        "weapon"
        {
                "font"      "WeaponIcons"
                "character" "l"
        }
        "weapon_s"
        {
                "font"      "WeaponIconsSelected"
                "character" "l"
        }
        "weapon_small"
        {
                "font"      "WeaponIconsSmall"
                "character" "l"
        }
        "ammo"
        {
                "font"      "WeaponIconsSmall"
                "character" "u"
        }
        "ammo2"
        {
                "font"      "WeaponIconsSmall"
                "character" "z"
        }
        "crosshair"
        {
                "font"      "Crosshairs"
                "character" "Q"
        }
        "autoaim"
        {
                "file"      "sprites/crosshairs"
                "x"         "0"
                "y"         "48"
                "width"     "24"
                "height"    "24"
        }
    }
}

Extra

You can now use the weapon in-game. Compile your mod and type this in the console:

sv_cheats 1
give weapon_<yourweaponname>

You should now be wielding your new weapon.

If you want to spawn with your new weapon add this in hl2mp_player.cpp below void CHL2MP_Player::GiveDefaultItems( void ):

GiveNamedItem( "weapon_ak47" );

And if you want to get your new weapon by the impulse 101 cheat command then add this in the same file but below void CHL2MP_Player::GiveAllItems( void ):

GiveNamedItem( "weapon_ak47" );

And you probably want to create default maps for your mod and add this weapon as a pickup, then add this into the .fgd file for your mod:

@PointClass base(Weapon) studio("models/weapons/w_rif_ak47.mdl") = weapon_ak47 : "AK47" []

To change the accuracy of your weapon then edit this part of the weapon_<yourweaponname>.cpp file:

static Vector cone = VECTOR_CONE_1DEGREES;
if ( m_bInZoom )
{
	if ( m_iStance == E_DUCK )
		cone = VECTOR_CONE_0DEGREES;
	if ( m_iStance == E_STAND )
		cone = VECTOR_CONE_1DEGREES;
	if ( m_iStance == E_MOVE )
		cone = VECTOR_CONE_1DEGREES;
	if ( m_iStance == E_RUN )
		cone = VECTOR_CONE_3DEGREES;
	if ( m_iStance == E_INJURED )
		cone = VECTOR_CONE_4DEGREES;
	if ( m_iStance == E_JUMP )
		cone = VECTOR_CONE_5DEGREES;
	if ( m_iStance == E_DYING )
		cone = VECTOR_CONE_10DEGREES;
}
else
{
	if ( m_iStance == E_DUCK )
		cone = VECTOR_CONE_1DEGREES;
	if ( m_iStance == E_STAND )
		cone = VECTOR_CONE_1DEGREES;
	if ( m_iStance == E_MOVE )
		cone = VECTOR_CONE_2DEGREES;
	if ( m_iStance == E_RUN )
		cone = VECTOR_CONE_3DEGREES;
	if ( m_iStance == E_INJURED )
		cone = VECTOR_CONE_4DEGREES;
	if ( m_iStance == E_JUMP )
		cone = VECTOR_CONE_5DEGREES;
	if ( m_iStance == E_DYING )
		cone = VECTOR_CONE_10DEGREES;
}

int iBurst = BURST;
if ( m_iBurst != iBurst )
{
	for ( int i = m_iBurst; i < iBurst; i++ )
	{
		cone += VECTOR_CONE_1DEGREES;
	}
}

A higher value means more imprecise accuracy, and bullets will more likely spread in all angles. A lower value is recommended.

To change the rate of fire edit this part of the weapon_<yourweaponname>.cpp file:

// Modify this to alter the rate of fire
#define ROF 0.075f // 800 rounds/min

To add different animations to your weapon (not default ones, but other animations implemented by Valve) edit this part of the weapon_<yourweaponname>.cpp file:

acttable_t CWeaponAK47::m_acttable[] =
{
	{ ACT_MP_STAND_IDLE,					ACT_HL2MP_IDLE_AR2,						false },
	{ ACT_MP_CROUCH_IDLE,					ACT_HL2MP_IDLE_CROUCH_AR2,				false },
	{ ACT_MP_RUN,							ACT_HL2MP_RUN_AR2,						false },
	{ ACT_MP_CROUCHWALK,					ACT_HL2MP_WALK_CROUCH_AR2,				false },
	{ ACT_MP_ATTACK_STAND_PRIMARYFIRE,		ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2,		false },
	{ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE,		ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2,		false },
	{ ACT_MP_RELOAD_STAND,					ACT_HL2MP_GESTURE_RELOAD_AR2,			false },
	{ ACT_MP_RELOAD_CROUCH,					ACT_HL2MP_GESTURE_RELOAD_AR2,			false },
	{ ACT_MP_JUMP,							ACT_HL2MP_JUMP_AR2,						false },
};

IMPLEMENT_ACTTABLE( CWeaponAK47 );

If you would rather have the SMG1 animations then change _AR2 to _SMG1 instead. And if you want to use the pistol animations change _AR2 to _PISTOL etc...

Finish

You have now successfully implemented a new weapon into your mod, I hope that this was understandable and easy to follow!

Disclaimer: This piece of code was originally created by Pendra for the mod 'X-Com - Last Hope 2' (ModDB)