Adding a new weapon to your mod: Difference between revisions
No edit summary |
GamerDude27 (talk | contribs) m (Consistent section capitalization, corrected section header size) |
||
Line 16: | Line 16: | ||
{{note|1='''Avoid using anything HL2MP related in terms of programming as much of the code for how weapon data is parsed is NOT compatible.'''}} | {{note|1='''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 (Single Player Only) === | ||
<source lang=cpp> | <source lang=cpp> | ||
Line 571: | Line 571: | ||
</source> | </source> | ||
== | === c_weapon__stubs_hl2.cpp (Single Player Only) === | ||
This file is located in 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 under line '35' | This file is located in 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 under line '35' | ||
Line 580: | Line 580: | ||
</source> | </source> | ||
=== | === hl2_gamerules.cpp (Single Player Only) === | ||
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 at line '118' by adding the following: | 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 at line '118' by adding the following: | ||
Line 602: | Line 602: | ||
</source> | </source> | ||
=== | === item_ammo.cpp (Single Player Only) === | ||
In this step we will declare the class of ammo used, and define the world model for the game to fetch. In this example the code starts at line '38'. | In this step we will declare the class of ammo used, and define the world model for the game to fetch. In this example the code starts at line '38'. | ||
Line 681: | Line 681: | ||
</Source> | </Source> | ||
=== | === items.h (Single Player Only) === | ||
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 ammount 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 '26'. | 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 ammount 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 '26'. | ||
Line 694: | Line 694: | ||
</source> | </source> | ||
=== | === weapon_mp5.txt (Single Player Only) === | ||
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. | 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. | ||
Line 788: | Line 788: | ||
</source> | </source> | ||
=== | === skill.cfg (Single Player Only) === | ||
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 is the data tables we will need to add: | 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 is the data tables we will need to add: | ||
Line 828: | Line 828: | ||
=== | === weapon_ak47.cpp (Multiplayer or HL2MP Only) === | ||
<source lang=cpp> | <source lang=cpp> | ||
Line 1,242: | Line 1,242: | ||
</source> | </source> | ||
=== | === weapon_hl2mpbasehlmpcombatweapon.h (Multiplayer or HL2MP Only) === | ||
Here you will declare the m_istance's. | Here you will declare the m_istance's. | ||
Line 1,260: | Line 1,260: | ||
</source> | </source> | ||
=== | === hl2mp_gamerules.cpp (Multiplayer or HL2MP Only) === | ||
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... | ||
Line 1,272: | Line 1,272: | ||
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 === | ||
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. | ||
Line 1,282: | Line 1,282: | ||
=== | === 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. | 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. | ||
Line 1,375: | Line 1,375: | ||
=== | === Extra === | ||
You can now use the weapon in-game. Compile your mod and type this in console: | You can now use the weapon in-game. Compile your mod and type this in console: | ||
Line 1,485: | Line 1,485: | ||
And if you want to use the pistol animations change _AR2 to _PISTOL... etc... | And if you want to use the pistol animations change _AR2 to _PISTOL... 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! |
Revision as of 18:53, 30 July 2020
This article will deal with creating a custom weapon for your mod. This was based on an existing article which recently was removed:



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 in the Server side of your project. In this example we will use "weapon_mp5" throughout SP part of the tutorial.



weapon_mp5.cpp (Single Player Only)
#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 "grenade_ar2.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;
class CWeaponMP5 : public CHLSelectFireMachineGun
{
DECLARE_DATADESC();
public:
DECLARE_CLASS( CWeaponMP5, CHLSelectFireMachineGun );
CWeaponMP5();
DECLARE_SERVERCLASS();
void Precache( void );
void AddViewKick( void );
void SecondaryAttack( void );
int GetMinBurst() { return 2; }
int GetMaxBurst() { return 5; }
virtual void Equip( CBaseCombatCharacter *pOwner );
bool Reload( void );
float GetFireRate( void ) { return 0.075f; } // 13.3hz
int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; }
int WeaponRangeAttack2Condition( float flDot, float flDist );
Activity GetPrimaryAttackActivity( void );
//============================================================
//OLD VALUE - Used by Default for weapons like the SMG1 & AR2
//============================================================
// virtual const Vector& GetBulletSpread( void )
// {
// static const Vector cone = VECTOR_CONE_3DEGREES;
// return cone;
// }
//=============================================================
//----New Values which allow our "spread" to change from button input from player SP Only
virtual const Vector& GetBulletSpread( void )
{
// Define "spread" parameters based on the "owner" and what they are doing
static Vector p_duckCone = VECTOR_CONE_2DEGREES;
static Vector npcCone = VECTOR_CONE_5DEGREES;
static Vector p_standCone = VECTOR_CONE_3DEGREES;
static Vector p_moveCone = VECTOR_CONE_4DEGREES;
static Vector p_runCone = VECTOR_CONE_6DEGREES;
static Vector p_jumpCone = 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 p_duckCone;
if ( pPlayer->m_nButtons & IN_FORWARD )
return p_moveCone;
if ( pPlayer->m_nButtons & IN_BACK )
return p_moveCone;
if ( pPlayer->m_nButtons & IN_MOVERIGHT )
return p_moveCone;
if ( pPlayer->m_nButtons & IN_MOVELEFT )
return p_moveCone;
if ( pPlayer->m_nButtons & IN_JUMP )
return p_jumpCone;
if ( pPlayer->m_nButtons & IN_SPEED )
return p_runCone;
if ( pPlayer->m_nButtons & IN_RUN )
return p_runCone;
else
return p_standCone;
}
//-----------------------------------------------------------------
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();
protected:
Vector m_vecTossVelocity;
float m_flNextGrenadeCheck;
};
IMPLEMENT_SERVERCLASS_ST(CWeaponMP5, DT_WeaponMP5)
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( weapon_mp5, CWeaponMP5 );
PRECACHE_WEAPON_REGISTER(weapon_mp5);
BEGIN_DATADESC( CWeaponMP5 )
DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ),
DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ),
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);
//=========================================================
CWeaponMP5::CWeaponMP5( )
{
m_fMinRange1 = 0;// No minimum range.
m_fMaxRange1 = 1400;
m_bAltFiresUnderwater = false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::Precache( void )
{
UTIL_PrecacheOther("grenade_ar2");
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 = m_iClip1 - 1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary )
{
// Ensure we have enough rounds in the clip
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;
/*//FIXME: Re-enable
case EVENT_WEAPON_AR2_GRENADE:
{
CAI_BaseNPC *npc = pOperator->MyNPCPointer();
Vector vecShootOrigin, vecShootDir;
vecShootOrigin = pOperator->Weapon_ShootPosition();
vecShootDir = npc->GetShootEnemyDir( vecShootOrigin );
Vector vecThrow = m_vecTossVelocity;
CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecShootOrigin, vec3_angle, npc );
pGrenade->SetAbsVelocity( vecThrow );
pGrenade->SetLocalAngularVelocity( QAngle( 0, 400, 0 ) );
pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY );
pGrenade->m_hOwner = npc;
pGrenade->m_pMyWeaponAR2 = this;
pGrenade->SetDamage(sk_npc_dmg_ar2_grenade.GetFloat());
// FIXME: arrgg ,this is hard coded into the weapon???
m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
m_iClip2--;
}
break;
*/
default:
BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Activity
//-----------------------------------------------------------------------------
Activity CWeaponMP5::GetPrimaryAttackActivity( void )
{
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;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CWeaponMP5::Reload( void )
{
bool fRet;
float fCacheTime = m_flNextSecondaryAttack;
fRet = DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD );
if ( fRet )
{
// Undo whatever the reload process has done to our secondary
// attack timer. We allow you to interrupt reloading to fire
// a grenade.
m_flNextSecondaryAttack = GetOwner()->m_flNextAttack = fCacheTime;
WeaponSound( RELOAD );
}
return fRet;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponMP5::AddViewKick( void )
{
#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( void )
{
// Only the player fires this way so we can cast
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
if ( pPlayer == NULL )
return;
//Must have ammo
if ( ( pPlayer->GetAmmoCount( m_iSecondaryAmmoType ) <= 0 ) || ( pPlayer->GetWaterLevel() == 3 ) )
{
SendWeaponAnim( ACT_VM_DRYFIRE );
BaseClass::WeaponSound( EMPTY );
m_flNextSecondaryAttack = gpGlobals->curtime + 0.5f;
return;
}
if( m_bInReload )
m_bInReload = false;
// MUST call sound before removing a round from the clip of a CMachineGun
BaseClass::WeaponSound( WPN_DOUBLE );
pPlayer->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAGS_NONE );
Vector vecSrc = pPlayer->Weapon_ShootPosition();
Vector vecThrow;
// Don't autoaim on grenade tosses
AngleVectors( pPlayer->EyeAngles() + pPlayer->GetPunchAngle(), &vecThrow );
VectorScale( vecThrow, 1000.0f, vecThrow );
//Create the grenade
QAngle angles;
VectorAngles( vecThrow, angles );
CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecSrc, angles, pPlayer );
pGrenade->SetAbsVelocity( vecThrow );
pGrenade->SetLocalAngularVelocity( RandomAngle( -400, 400 ) );
pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
pGrenade->SetThrower( GetOwner() );
pGrenade->SetDamage( sk_plr_dmg_smg1_grenade.GetFloat() );
SendWeaponAnim( ACT_VM_SECONDARYATTACK );
CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON );
// player "shoot" animation
pPlayer->SetAnimation( PLAYER_ATTACK1 );
// Decrease ammo
pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType );
// Can shoot again immediately
m_flNextPrimaryAttack = gpGlobals->curtime + 0.5f;
// Can blow up after a short delay (so have time to release mouse button)
m_flNextSecondaryAttack = gpGlobals->curtime + 1.0f;
// Register a muzzleflash for the AI.
pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 0.5 );
m_iSecondaryAttacks++;
gamestats->Event_WeaponFired( pPlayer, false, GetClassname() );
}
#define COMBINE_MIN_GRENADE_CLEAR_DIST 256
//-----------------------------------------------------------------------------
// Purpose:
// Input : flDot -
// flDist -
// Output : int
//-----------------------------------------------------------------------------
int CWeaponMP5::WeaponRangeAttack2Condition( float flDot, float flDist )
{
CAI_BaseNPC *npcOwner = GetOwner()->MyNPCPointer();
return COND_NONE;
/*
// --------------------------------------------------------
// Assume things haven't changed too much since last time
// --------------------------------------------------------
if (gpGlobals->curtime < m_flNextGrenadeCheck )
return m_lastGrenadeCondition;
*/
// -----------------------
// If moving, don't check.
// -----------------------
if ( npcOwner->IsMoving())
return COND_NONE;
CBaseEntity *pEnemy = npcOwner->GetEnemy();
if (!pEnemy)
return COND_NONE;
Vector vecEnemyLKP = npcOwner->GetEnemyLKP();
if ( !( pEnemy->GetFlags() & FL_ONGROUND ) && pEnemy->GetWaterLevel() == 0 && vecEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().z) )
{
//!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to
// be grenaded.
// don't throw grenades at anything that isn't on the ground!
return COND_NONE;
}
// --------------------------------------
// Get target vector
// --------------------------------------
Vector vecTarget;
if (random->RandomInt(0,1))
{
// magically know where they are
vecTarget = pEnemy->WorldSpaceCenter();
}
else
{
// toss it to where you last saw them
vecTarget = vecEnemyLKP;
}
// vecTarget = m_vecEnemyLKP + (pEnemy->BodyTarget( GetLocalOrigin() ) - pEnemy->GetLocalOrigin());
// estimate position
// vecTarget = vecTarget + pEnemy->m_vecVelocity * 2;
if ( ( vecTarget - npcOwner->GetLocalOrigin() ).Length2D() <= COMBINE_MIN_GRENADE_CLEAR_DIST )
{
// crap, I don't want to blow myself up
m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
return (COND_NONE);
}
// ---------------------------------------------------------------------
// Are any friendlies near the intended grenade impact area?
// ---------------------------------------------------------------------
CBaseEntity *pTarget = NULL;
while ( ( pTarget = gEntList.FindEntityInSphere( pTarget, vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST ) ) != NULL )
{
//Check to see if the default relationship is hatred, and if so intensify that
if ( npcOwner->IRelationType( pTarget ) == D_LI )
{
// crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while.
m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
return (COND_WEAPON_BLOCKED_BY_FRIEND);
}
}
// ---------------------------------------------------------------------
// Check that throw is legal and clear
// ---------------------------------------------------------------------
// FIXME: speed is based on difficulty...
Vector vecToss = VecCheckThrow( this, npcOwner->GetLocalOrigin() + Vector(0,0,60), vecTarget, 600.0, 0.5 );
if ( vecToss != vec3_origin )
{
m_vecTossVelocity = vecToss;
// don't check again for a while.
// JAY: HL1 keeps checking - test?
//m_flNextGrenadeCheck = gpGlobals->curtime;
m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second.
return COND_CAN_RANGE_ATTACK2;
}
else
{
// don't check again for a while.
m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
return COND_WEAPON_SIGHT_OCCLUDED;
}
}
//-----------------------------------------------------------------------------
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 (Single Player Only)
This file is located in 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 under line '35'
STUB_WEAPON_CLASS( weapon_mp5, WeaponMP5, C_HLSelectFireMachineGun );
hl2_gamerules.cpp (Single Player Only)
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 at line '118' 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 '1818' and add the following:

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 (Single Player Only)
In this step we will declare the class of ammo used, and define the world model for the game to fetch. In this example the code starts at line '38'.

// ========================================================================
// >> Box 9mm MP5 Rounds
// ========================================================================
class CItem_BoxMP5Rounds : public CItem
{
public:
DECLARE_CLASS( CItem_BoxMP5Rounds, CItem );
void Spawn( void )
{
Precache( );
SetModel( "models/items/boxsrounds.mdl" );
BaseClass::Spawn( );
}
void Precache( void )
{
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( void )
{
Precache( );
SetModel( "models/items/boxsrounds.mdl" );
BaseClass::Spawn( );
}
void Precache( void )
{
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 (Single Player Only)
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 ammount 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 '26'.

#define SIZE_AMMO_MP5 35
#define SIZE_AMMO_MP5_LARGE 110
weapon_mp5.txt (Single Player Only)
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.

// 9x19mm Sub Machinegun
WeaponData
{
// Weapon data is loaded by both the Game and Client DLLs.
"BuiltRightHanded" "0"
"AllowFlipping" "1"
"printname" "9x19MM SUBMACHINE GUN"
"viewmodel" "models/weapons/v_mp5.mdl"
"playermodel" "models/weapons/w_mp5.mdl" //FIXME:
"anim_prefix" "smg2"
"bucket" "2"
"bucket_position" "2"
"bucket_360" "1"
"bucket_position_360" "0"
"clip_size" "30"
"clip2_size" "-1"
"default_clip" "40"
"default_clip2" "-1"
"primary_ammo" "Mp5"
"secondary_ammo" "SMG1_Grenade"
"weight" "3"
"rumble" "3"
"item_flags" "0"
// 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 (Single Player Only)
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 is the data tables we will need to add:
sk_plr_dmg_mp5 "11"
sk_npc_dmg_mp5 "9"
sk_max_mp5 "180"
Extra (Single Player Only)
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 6165 add the following line
GiveNamedItem( "weapon_mp5" );
Tips





The Code (Mutiplayer or HL2MP Version)
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"




weapon_ak47.cpp (Multiplayer or HL2MP Only)
#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, 60 Sec / 800 Rounds = 0.075f
//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(void);
DECLARE_NETWORKCLASS();
DECLARE_PREDICTABLE();
void Precache( void );
void ItemPostFrame( void );
void ItemPreFrame( void );
void ItemBusyFrame( void );
void PrimaryAttack( void );
void AddViewKick( void );
void DryFire( void );
void GetStance( void );
bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); // Required so that you un-zoom when switching weapons
Activity GetPrimaryAttackActivity( void );
virtual bool Reload( void );
int GetMinBurst() { return 2; }
int GetMaxBurst() { return 5; }
float GetFireRate( void ) { return ROF; }
//modify this part to control the general accuracy of the gun
virtual const Vector& GetBulletSpread( void )
{
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.
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;
}
void ToggleZoom( void );
void CheckZoomToggle( void );
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( void )
{
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( void )
{
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose: The gun is empty, plays a clicking noise with a dryfire anim
//-----------------------------------------------------------------------------
void CWeaponAK47::DryFire( void )
{
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( void )
{
//do we have any bullets left from the current burst cycle?
if (m_iBurst!=0)
{
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
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.
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( void )
{
GetStance();
BaseClass::ItemPreFrame();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemBusyFrame( void )
{
// Allow zoom toggling even when we're reloading
CheckZoomToggle();
BaseClass::ItemBusyFrame();
}
//-----------------------------------------------------------------------------
// Purpose: Allows firing as fast as button is pressed
//-----------------------------------------------------------------------------
void CWeaponAK47::ItemPostFrame( void )
{
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( void )
{
if (m_iBurst!=0)
{ return ACT_VM_PRIMARYATTACK; }
else
{ return ACT_VM_IDLE; }
}
//-----------------------------------------------------------------------------
// Purpose: The gun is being reloaded
//-----------------------------------------------------------------------------
bool CWeaponAK47::Reload( void )
{
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 viewkick
//-----------------------------------------------------------------------------
void CWeaponAK47::AddViewKick( void )
{
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( void )
{
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;
// Send a message to hide the scope
/* CSingleUserRecipientFilter filter(pPlayer);
UserMessageBegin(filter, "ShowScope");
WRITE_BYTE(0);
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
//-----------------------------------------------------------------------------
void CWeaponAK47::CheckZoomToggle( void )
{
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
if ( pPlayer && (pPlayer->m_afButtonPressed & IN_ATTACK2))
{ ToggleZoom(); }
}
//-----------------------------------------------------------------------------
// Purpose: Get the current stance/status of the player
//-----------------------------------------------------------------------------
void CWeaponAK47::GetStance( void )
{
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 (Multiplayer or HL2MP Only)
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 (Multiplayer or HL2MP Only)
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 1863:
def.AddAmmoType("Rifle", DMG_BULLET, TRACER_LINE_AND_WHIZ, 0, 0, 500, BULLET_IMPULSE(200, 1225), 0 );
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. 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 );
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>" in this case I called mine "weapon_ak47" Here is an example:
WeaponData
{
// Weapon data is loaded by both the Game and Client DLLs.
"printname" "AK47"
"viewmodel" "models/weapons/v_rif_ak47.mdl" // Client view model
"playermodel" "models/weapons/w_rif_ak47.mdl" // Server world model
"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.
"bucket" "5" // The horizontal direction.
"bucket_position" "3" // The vertical direction.
"clip_size" "30" // How many bullets you can fire before you have to reload.
"default_clip" "500" // How much ammo this weapon can carry.
"clip2_size" "-1"
"default_clip2" "-1"
"primary_ammo" "Rifle" // The ammotype for the weapon, this can be found in hl2mp_gamerules.cpp: def.AddAmmoType(
"secondary_ammo" "None"
"weight" "6" // If the weight is higher than your current weapon you will automatically switch to that weapon when you pick it up.
"item_flags" "0"
"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)
SoundData
{
"special1" "Weapon_CombineGuard.Special1"
"empty" "Weapon_IRifle.Empty"
"double_shot" "Weapon_AK47.Single"
"reload" "Weapon_AK47.Reload" // Sound triggered when reloading. Activate this in the game_sounds_weapons.
"single_shot" "Weapon_AK47.Single" // Sound triggered when shooting. -=-
// 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 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 impulse101 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;}
}
if (!m_bInZoom)
{
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 bs=BURST;
if (m_iBurst!=bs)
{
//for (int i=0;i<(bs-m_iBurst);i++)
for (int i = m_iBurst; i < bs; i++)
{
cone=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.080f //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