Cleanup is going to take awhile, so the site is back up but editing has been disabled.
Cloning MP SDK Weapon
From HalfLife 2 Knowledge Base
[edit]
Cloning a Weapon
This tutorial has been tested by 1 person, have a go at it, and send problems, comments and ideas to criticalimpact@gmail.com I am mainly have the problem with includes like so.
Making a new weapon(cloning) using the MP SDK.
Start off by creating a new project through the Steam SDK menu or if you already have one use that instead.
We will be cloning the SMG into an AUG. It won’t have any new sounds or models but they are easy to change if you wish.
I will be refering to Microsoft Visual Studio C++ 2003. The steps involved when using a different environment should not be that different. Go into Steam\SteamApps\SourceMods\yourmodsname\src\game_shared\hl2mp and located weapon_smg1.cpp. Copy this and rename it to weapon_aug.cpp.
If you want you can replace aug with another name.
Go back into MSVC++ and goto the client project.
Go into Client->Source Files->HL2MP->Weapons->Right click and add existing item. Select weapon_aug.cpp and add it.
Do the same to hl project and add it in the same location.
You should now have the file added in the same place in client and hl.
Now onto editing the actual weapon.
Here is my copy of weapon_aug.cpp, I believe that using find/replace should work.
//========= Copyright © 1996-2005, 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 "grenade_ar2.h"
#include "hl2mp_player.h"
#include "basegrenade_shared.h"
#include "AI_BaseNPC.h"
#endif
#include "weapon_hl2mpbase.h"
#include "weapon_hl2mpbase_machinegun.h"
#ifdef CLIENT_DLL
#define CWeaponAUG C_WeaponAUG
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define AUG_GRENADE_DAMAGE 100.0f
#define AUG_GRENADE_RADIUS 250.0f
class CWeaponAUG : public CHL2MPMachineGun
{
public:
DECLARE_CLASS( CWeaponAUG, CHL2MPMachineGun );
CWeaponAUG();
DECLARE_NETWORKCLASS();
DECLARE_PREDICTABLE();
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
Activity GetPrimaryAttackActivity( void );
virtual const Vector& GetBulletSpread( void )
{
static const Vector cone = VECTOR_CONE_5DEGREES;
return cone;
}
const WeaponProficiencyInfo_t *GetProficiencyValues();
#ifndef CLIENT_DLL
void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
{
switch( pEvent->event )
{
case EVENT_WEAPON_SMG1:
{
Vector vecShootOrigin, vecShootDir;
QAngle angDiscard;
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 );
// FIXME: use the returned number of bullets to account for >10hz firerate
WeaponSoundRealtime( SINGLE_NPC );
CSoundEnt::InsertSound( SOUND_COMBAT, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pOperator );
pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED,
MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), 0 );
pOperator->DoMuzzleFlash();
m_iClip1 = m_iClip1 - 1;
}
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;
}
}
#endif
#ifndef CLIENT_DLL
DECLARE_ACTTABLE();
#endif
protected:
Vector m_vecTossVelocity;
float m_flNextGrenadeCheck;
private:
CWeaponAUG( const CWeaponAUG & );
};
IMPLEMENT_NETWORKCLASS_ALIASED( WeaponAUG, DT_WeaponAUG )
BEGIN_NETWORK_TABLE( CWeaponAUG, DT_WeaponAUG )
END_NETWORK_TABLE()
BEGIN_PREDICTION_DATA( CWeaponAUG )
END_PREDICTION_DATA()
LINK_ENTITY_TO_CLASS( weapon_aug, CWeaponAUG );
PRECACHE_WEAPON_REGISTER(weapon_aug);
#ifndef CLIENT_DLL
acttable_t CWeaponAUG::m_acttable[] =
{
{ ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SMG1, false },
{ ACT_HL2MP_RUN, ACT_HL2MP_RUN_SMG1, false },
{ ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SMG1, false },
{ ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SMG1, false },
{ ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1, false },
{ ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SMG1, false },
{ ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SMG1, false },
{ ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, false },
{ 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(CWeaponAUG);
#endif
//=========================================================
CWeaponAUG::CWeaponAUG( )
{
m_fMinRange1 = 0;// No minimum range.
m_fMaxRange1 = 1400;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponAUG::Precache( void )
{
#ifndef CLIENT_DLL
UTIL_PrecacheOther("grenade_ar2");
#endif
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose: Give this weapon longer range when wielded by an ally NPC.
//-----------------------------------------------------------------------------
void CWeaponAUG::Equip( CBaseCombatCharacter *pOwner )
{
m_fMaxRange1 = 1400;
BaseClass::Equip( pOwner );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Activity
//-----------------------------------------------------------------------------
Activity CWeaponAUG::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 CWeaponAUG::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 CWeaponAUG::AddViewKick( void )
{
#define EASY_DAMPEN 0.5f
#define MAX_VERTICAL_KICK 1.0f //Degrees
#define SLIDE_LIMIT 2.0f //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 CWeaponAUG::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 );
Vector vecSrc = pPlayer->Weapon_ShootPosition();
Vector vecThrow;
// Don't autoaim on grenade tosses
AngleVectors( pPlayer->EyeAngles() + pPlayer->GetPunchAngle(), &vecThrow );
VectorScale( vecThrow, 1000.0f, vecThrow );
#ifndef CLIENT_DLL
//Create the grenade
CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecSrc, vec3_angle, pPlayer );
pGrenade->SetAbsVelocity( vecThrow );
pGrenade->SetLocalAngularVelocity( RandomAngle( -400, 400 ) );
pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
pGrenade->SetThrower( GetOwner() );
pGrenade->SetDamage( AUG_GRENADE_DAMAGE );
pGrenade->SetDamageRadius( AUG_GRENADE_RADIUS );
#endif
SendWeaponAnim( ACT_VM_SECONDARYATTACK );
// 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;
}
//-----------------------------------------------------------------------------
const WeaponProficiencyInfo_t *CWeaponAUG::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;
}
Basically change all references to SMG1 to AUG except for the animations that are listed below
acttable_t CWeaponAUG::m_acttable[] =
There are a couple of other files you have to edit.
Hl->Source Files->HL2 DLL->HL2_Player.cpp
Locate BaseClass::GiveAmmo( 255, "SMG1");
Copy that and add
BaseClass::GiveAmmo( 255, "AUG");
Also below that find GiveNamedItem( "weapon_smg1" );
Copy that and add
GiveNamedItem( "weapon_aug" );
Hl->Source Files->HL2MP->HL2mp_Player.cpp
Locate CBasePlayer::GiveAmmo( 255, "SMG1");
Copy that and add
CBasePlayer::GiveAmmo( 255, "AUG");
Also below that find GiveNamedItem( "weapon_smg1" );
Copy that and add
GiveNamedItem( "weapon_aug" );
Hl->Source Files->items.h
Find
#define SIZE_AMMO_SMG1 45
#define SIZE_AMMO_SMG1_LARGE 225
#define SIZE_AMMO_SMG1_GRENADE 1
and create
#define SIZE_AMMO_AUG 45
#define SIZE_AMMO_AUG_LARGE 225
#define SIZE_AMMO_AUG_GRENADE 1
Hl->Source Files->HL2 DLL->item_ammo.cpp
Copy
class CItem_BoxMRounds : public CItem
{
public:
DECLARE_CLASS( CItem_BoxMRounds, CItem );
void Spawn( void )
{
Precache( );
SetModel( "models/items/boxmrounds.mdl");
BaseClass::Spawn( );
}
void Precache( void )
{
PrecacheModel ("models/items/boxmrounds.mdl");
}
bool MyTouch( CBasePlayer *pPlayer )
{
if (ITEM_GiveAmmo( pPlayer, SIZE_AMMO_SMG1, "SMG1"))
{
if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
{
UTIL_Remove(this);
}
return true;
}
return false;
}
};
LINK_ENTITY_TO_CLASS(item_box_mrounds, CItem_BoxMRounds);
LINK_ENTITY_TO_CLASS(item_ammo_smg1, CItem_BoxMRounds);
// ========================================================================
// >> LargeBoxMRounds
// ========================================================================
class CItem_LargeBoxMRounds : public CItem
{
public:
DECLARE_CLASS( CItem_LargeBoxMRounds, CItem );
void Spawn( void )
{
Precache( );
SetModel( "models/items/boxmrounds.mdl");
BaseClass::Spawn( );
}
void Precache( void )
{
PrecacheModel ("models/items/boxmrounds.mdl");
}
bool MyTouch( CBasePlayer *pPlayer )
{
if (ITEM_GiveAmmo( pPlayer, SIZE_AMMO_SMG1_LARGE, "SMG1"))
{
if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
{
UTIL_Remove(this);
}
return true;
}
return false;
}
};
LINK_ENTITY_TO_CLASS(item_large_box_mrounds, CItem_LargeBoxMRounds);
LINK_ENTITY_TO_CLASS(item_ammo_smg1_large, CItem_LargeBoxMRounds);
Replace it with
class CItem_BoxAUGRounds : public CItem
{
public:
DECLARE_CLASS( CItem_BoxAUGRounds, CItem );
void Spawn( void )
{
Precache( );
SetModel( "models/items/boxmrounds.mdl");
BaseClass::Spawn( );
}
void Precache( void )
{
PrecacheModel ("models/items/boxmrounds.mdl");
}
bool MyTouch( CBasePlayer *pPlayer )
{
if (ITEM_GiveAmmo( pPlayer, SIZE_AMMO_AUG, "AUG"))
{
if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
{
UTIL_Remove(this);
}
return true;
}
return false;
}
};
LINK_ENTITY_TO_CLASS(item_box_augrounds, CItem_BoxAUGRounds);
LINK_ENTITY_TO_CLASS(item_ammo_aug, CItem_BoxAUGRounds);
// ========================================================================
// >> LargeBoxMRounds
// ========================================================================
class CItem_LargeBoxAUGRounds : public CItem
{
public:
DECLARE_CLASS( CItem_LargeBoxAUGRounds, CItem );
void Spawn( void )
{
Precache( );
SetModel( "models/items/boxmrounds.mdl");
BaseClass::Spawn( );
}
void Precache( void )
{
PrecacheModel ("models/items/boxmrounds.mdl");
}
bool MyTouch( CBasePlayer *pPlayer )
{
if (ITEM_GiveAmmo( pPlayer, SIZE_AMMO_AUG_LARGE, "AUG"))
{
if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
{
UTIL_Remove(this);
}
return true;
}
return false;
}
};
LINK_ENTITY_TO_CLASS(item_large_box_augrounds, CItem_LargeBoxAUGRounds);
LINK_ENTITY_TO_CLASS(item_ammo_aug_large, CItem_LargeBoxAUGRounds);
Now onto creating the script files.
Goto your modsname\scripts and copy weapon_smg1.txt and change the name to weapon_aug.txt
This is what mine looked like after I modified it.
// AUG
WeaponData
{
// Weapon data is loaded by both the Game and Client DLLs.
"printname" "#HL2_AUG"
"viewmodel" "models/weapons/v_smg1.mdl"
"playermodel" "models/weapons/w_smg1.mdl" //FIXME:
"anim_prefix" "smg2"
"bucket" "2"
"bucket_position" "4"
"clip_size" "45"
"clip2_size" "-1"
"default_clip" "45"
"default_clip2" "-1"
"primary_ammo" " SMG1"
"secondary_ammo" "SMG1_Grenade"
"weight" "3"
"item_flags" "0"
"damage" "5"
// Sounds for the weapon. There is a max of 16 sounds per category (i.e. max 16 "single_shot" sounds)
SoundData
{
"reload" "Weapon_SMG1.Reload"
"reload_npc" "Weapon_SMG1.NPC_Reload"
"empty" "Weapon_SMG1.Empty"
"single_shot" "Weapon_SMG1.Single"
"single_shot_npc" "Weapon_SMG1.NPC_Single"
"special1" "Weapon_SMG1.Special1"
"special2" "Weapon_SMG1.Special2"
"double_shot" "Weapon_SMG1.Double"
"burst" "Weapon_SMG1.Burst"
}
// Weapon Sprite data is loaded by the Client DLL.
TextureData
{
"weapon"
{
"font" "WeaponIcons"
"character" "a"
}
"weapon_s"
{
"font" "WeaponIconsSelected"
"character" "a"
}
"ammo"
{
"font" "WeaponIcons"
"character" "r"
}
"ammo2"
{
"font" "WeaponIcons"
"character" "t"
}
"crosshair"
{
"font" "Crosshairs"
"character" "Q"
}
"autoaim"
{
"file" "sprites/crosshairs"
"x" "0"
"y" "48"
"width" "24"
"height" "24"
}
}
}

