Creating a new Weapon

From Pragma
Jump to: navigation, search

In this tutorial we will be recreating the SMG from Half-Life 2.
You can find a preview of the weapon here: https://youtu.be/wACMXMkY8yg
First of all you will need the resource pack, which includes the model and the sounds required for the SMG. Then create a new directory in "addons" called "tut_hl2_smg" and extract the archive here.

File Structure

Open the "tut_hl2_smg" directory and create the path "tut_hl2_smg/lua/weapons/weapon_smg1", then create three new files inside that directory: "init.lua", "cl_init.lua" and "shared.lua". init.lua will contain anything that has to be executed serverside, cl_init.lua everything clientside, and shared.lua anything that is used on both sides.
Next we'll fill the files with the minimum required code:

Initialization

init.lua

resource.add_lua_file("cl_init.lua") -- Makes sure "cl_init.lua" is transferred to clients if they don't have it

include("shared.lua")
WeaponSMG1.Type = Entity.TYPE_SHARED -- Our entity has a serverside and a clientside part

init.lua is automatically executed serverside when the game is loaded.
We don't need to do anything special for now, so we'll just include our shared code, and set the entity type.
Entity.TYPE_SHARED will tell the game that the entity actually has a clientside part, otherwise we wouldn't be able to see or fire the weapon.
There's also Entity.TYPE_DEFAULT, which can be used for serverside-only entities. This is a special case, which you don't usually need for weapons.
You can also create a clientside-only entity, by simply not adding a init.lua in the first place, but again, this is usually not useful for weapons.

cl_init.lua

include("shared.lua")

cl_init.lua is automatically executed clientside when the game is loaded.
We don't need to do anything special for now, so all we need to do is include our shared code.

shared.lua

util.register_class("WeaponSMG1",BaseWeapon) -- Our new weapon has to be derived directly (or indirectly) from BaseWeapon

engine.load_sound_scripts("fx_weapon_smg1.txt",true)

WeaponSMG1.ViewModel = "weapons/v_smg1.wmd"
WeaponSMG1.WorldModel = "weapons/w_smg1.wmd"

WeaponSMG1.Primary = {}
WeaponSMG1.Primary.AmmoType = "smg1"
WeaponSMG1.Primary.MaxClipSize = 45
WeaponSMG1.Primary.AttackDelay = 0.1
WeaponSMG1.Primary.Automatic = true

WeaponSMG1.Secondary = {}
WeaponSMG1.Secondary.AmmoType = "smg1_grenade"
WeaponSMG1.Secondary.AttackDelay = 0.4
WeaponSMG1.Secondary.Automatic = true

WeaponSMG1.ReloadDelay = 1.0

function WeaponSMG1:__init() -- The constructor for our weapon
	BaseWeapon.__init(self)
end

function WeaponSMG1:Initialize()
	if(CLIENT == true) then
		self:SetViewModel(self.ViewModel)
		self:SetHideWorldModelInFirstPerson(true)
	end
	self:SetAutomaticPrimary(true)
	self:SetAutomaticSecondary(true)
	if(SERVER == true) then
		self:SetPrimaryAmmoType(self.Primary.AmmoType)
		self:SetSecondaryAmmoType(self.Secondary.AmmoType)
		self:SetMaxPrimaryClipSize(self.Primary.MaxClipSize)

		self:SetModel(self.WorldModel)
	end
end
ents.register("weapon_smg1",WeaponSMG1)

All entities in Lua are class-based, so we have to declare our class first. That's what this line is for:

util.register_class("WeaponSMG1",BaseWeapon)

The first argument is the name for the Lua-class we want to use. This name is not particularly important, but has to be unique. This name also has nothing to do with the class name we'll use later to actually spawn the weapon.
The second argument specifies the class we want to derive from. In the case of a weapon, this always has to be either BaseWeapon, or a class derived from BaseWeapon!

engine.load_sound_scripts("fx_weapon_smg1.txt",true)

To play the sounds for our weapons, we have to load in the soundscript file (Included in the resource pack), which will automatically precache all of the sounds defined within it.
After that follow some constant class definitions, which can be used to adjust the weapon quickly later on if needed:

WeaponSMG1.ViewModel = "weapons/v_smg1.wmd"
WeaponSMG1.WorldModel = "weapons/w_smg1.wmd"

The ViewModel is the model which shows up in first-person for the player that's holding the weapon.
This model usually has at least the hands, and sometimes the weapon itself.
The WorldModel on the other hand is the model which shows up for other players, or if the player holding the weapon is in third-person.

WeaponSMG1.Primary.AmmoType = "smg1"
WeaponSMG1.Primary.MaxClipSize = 45

WeaponSMG1.Secondary.AmmoType = "smg1_grenade"

The SMG will have two fire modes with different ammunitions, which are specified here. We'll have to actually register the ammunition as well, but we'll do that at a later point.
We can also set the clip-sizes for the firing modes here. This does not affect how much ammunition of this type the player is allowed to carry in total!
The secondary fire doesn't use an actual clip, which is why we won't specify a clip size for it. More on that later.

WeaponSMG1.Primary.AttackDelay = 0.1
WeaponSMG1.Secondary.AttackDelay = 0.4

WeaponSMG1.ReloadDelay = 1.0

When the weapon has been fired or reloaded, the player won't be able to use it again until the specified AttackDelay / ReloadDelay has passed.

WeaponSMG1.Primary.Automatic = true
WeaponSMG1.Secondary.Automatic = true

We want both of our fire modes to be automatic. This means that the player can just hold down the fire-button, and the weapon will continue firing automatically.
If a fire mode is set to non-automatic, the player will have to press and release the fire-button every time.

function WeaponSMG1:Initialize()
	if(CLIENT == true) then
		self:SetViewModel(self.ViewModel)
		self:SetHideWorldModelInFirstPerson(true)
	end
	[...]
end

Initialize is called when an instance of the weapon was created (But not spawned yet!). So far we've only declared some variables, we will now apply them to our weapon here!
First, we'll assign the view-model. Usually, when in first-person, the world-model will be attached to the view-model and drawn in first-person as well. This is useful if the view-model doesn't include the actual weapon.
Since in our case it does include the weapon, we don't need to draw the world-model in first-person. This is what the SetHideWorldModelInFirstPerson-call is for.

function WeaponSMG1:Initialize()
	[...]
	self:SetAutomaticPrimary(self.Primary.Automatic)
	self:SetAutomaticSecondary(self.Secondary.Automatic)
	if(SERVER == true) then
		self:SetPrimaryAmmoType(self.Primary.AmmoType)
		self:SetSecondaryAmmoType(self.Secondary.AmmoType)
		self:SetMaxPrimaryClipSize(45)
		self:SetMaxSecondaryClipSize(3)

		self:SetModel(self.WorldModel)
	end
	[...]
end

Here we give the weapon some information on how to handle the fire modes. Note that some methods have to be called shared (e.g. SetAutomaticPrimary), and some only serverside (e.g. SetPrimaryAmmoType). You can check out the individual page on the wiki to find out where each method belongs.
Lastly, we'll also assign the world-model for the weapon.

Now there's only one thing left to do to complete the initialization for the weapon:

ents.register("weapon_smg1",WeaponSMG1)

This will assign the Lua-class WeaponSMG1 to the entity class weapon_smg1, and we can now equip ourselves with the weapon using the console command "give_weapon weapon_smg1".
You should already be able to see the weapon in first-person, but of course it will not do anything yet. We'll come to implementing the actual fire-modes in the following sections.

Primary Fire Mode

We'll start with the primary fire for the weapon. This is relatively simple, as the weapon should just fire some bullets.
First, let's go back to our Initialize functions, and add some information about our bullets:

function WeaponSMG1:Initialize()
	[...]
	local bulletInfo = BulletInfo()
	bulletInfo.inflictor = self
	bulletInfo.distance = 8192
	bulletInfo.spread = EulerAngles(3,3,0)
	bulletInfo.bulletCount = 1
	bulletInfo.tracerCount = 0
	bulletInfo.ammoType = self.Primary.AmmoType
	self.m_bulletInfo = bulletInfo
end

Explanation:

bulletInfo.inflictor = self

The inflictor is the entity which is the cause of the fired bullets, which in our case is always the weapon.

bulletInfo.distance = 8192

This is the maximum distance the bullet can travel and hit targets.

bulletInfo.spread = EulerAngles(3,3,0)

The maximum spread of the bullets. The roll-value of the angles is ignored.
Whenever a bullet is fired, a random spread will be generated, which in our case can be in the range [-3,3] for pitch and [-3,3] for yaw. EulerAngles(0,0,0) would mean the weapon has perfect accuracy.

bulletInfo.bulletCount = 1

We want to fire a single bullet every time the weapon is fired. If you wanted to make a shotgun, for example, you could change this value to fire multiple bullets at once.

bulletInfo.tracerCount = 0

This value specifies the frequency of a tracer effect appearing. A value of 1 means every bullet has a tracer, 2 means every second bullet has a tracer, 0 = no tracer, etc.
We do want a tracer, however that requires some extra work, which we will cover in a later section.

bulletInfo.ammoType = self.Primary.AmmoType

The ammo type that the bullet uses. This determines the damage, force, etc.

Now we just need to tell our weapon when to fire the bullet. To do so, we can use the OnPrimaryAttack-hook, which is called whenever the owner of the weapon wants to fire it:

function WeaponSMG1:OnPrimaryAttack()
	if(self:IsPrimaryClipEmpty() == true) then
		if(CLIENT == true) then
			self:EmitSound("weapon_smg1.empty",1.0,1.0)
			self:PlayViewActivity(Animation.ACT_VM_TERTIARY_ATTACK,Entity.FPLAYANIM_RESET)
		end
		self:SetNextAttack(time.cur_time() +self.Primary.AttackDelay)
		return
	end
	local owner = self:GetOwner()
	self.m_bulletInfo.attacker = (owner ~= nil) and owner or ents.get_null()
	self:FireBullets(self.m_bulletInfo)
	if(CLIENT == true) then
		self:EmitSound("weapon_smg1.fire1",1.0,1.0)
		self:PlayViewActivity(Animation.ACT_VM_PRIMARY_ATTACK,Entity.FPLAYANIM_RESET)
	else
		self:RemovePrimaryClip()
	end
	self:SetNextAttack(time.cur_time() +self.Primary.AttackDelay)
end

Explanation:

function WeaponSMG1:OnPrimaryAttack()
	if(self:IsPrimaryClipEmpty() == true) then
		[...]
	end
	[...]
end

It shouldn't be possible to fire the weapon, if the weapon's clip is empty, so we'll have to catch that case first.

function WeaponSMG1:OnPrimaryAttack()
	if(self:IsPrimaryClipEmpty() == true) then
		if(CLIENT == true) then
			self:EmitSound("weapon_smg1.empty",1.0,1.0)
			self:PlayViewActivity(Animation.ACT_VM_TERTIARY_ATTACK,Entity.FPLAYANIM_RESET)
		end
		[...]
	end
	[...]
end

If the weapon is empty, we want to give the player a hint, by playing an "empty" sound and animation.
Usually when trying to play animation, nothing will happen if the animation was already playing. In our case we want to restart the animation if that happens, which is what the Entity.FPLAYANIM_RESET flag is for.

function WeaponSMG1:OnPrimaryAttack()
	if(self:IsPrimaryClipEmpty() == true) then
		[...]
		self:SetNextAttack(time.cur_time() +self.Primary.AttackDelay)
		return
	end
	[...]
end

SetNextAttack will ensure that OnPrimaryAttack (or OnSecondaryAttack, etc.) will not be called until the specified time has passed. Without this, we would hear continuous clicking noises while the player is holding down the fire button.

function WeaponSMG1:OnPrimaryAttack()
	[...]
	local owner = self:GetOwner()
	self.m_bulletInfo.attacker = (owner ~= nil) and owner or ents.get_null()
	[...]
end

Here we specify the attacker of the bullet. The attacker is whoever triggered the bullet in the first place, which will usually be the owner of the weapon.
Since the weapon doesn't necessarily have an owner (e.g. it's firing while on the ground for whatever reason), we have to catch that case as well.

function WeaponSMG1:OnPrimaryAttack()
	[...]
	self:FireBullets(self.m_bulletInfo)
	[...]
end

Here we actually fire our bullet. This will create a tracer-effect (as specified earlier), an impact effect wherever the bullet has hit, and deal damage according to our ammunition type to whatever was hit.
This will not create a muzzle-flash, we will deal with that at a later point.
Note: FireBullets has to be called both serverside and clientside (=shared), otherwise it will not work properly!

function WeaponSMG1:OnPrimaryAttack()
	[...]
	if(CLIENT == true) then
		self:EmitSound("weapon_smg1.fire1",1.0,1.0)
		self:PlayViewActivity(Animation.ACT_VM_PRIMARY_ATTACK,Entity.FPLAYANIM_RESET)
	else
		self:RemovePrimaryClip()
	end
	self:SetNextAttack(time.cur_time() +self.Primary.AttackDelay)
end

We still need to give some feedback, so we'll play a firing-sound and an appropriate animation.
On the server, we call RemovePrimaryClip to remove a single bullet from our clip count.

Two things are still missing: We still need to give the owner ammunition to be able to fire the weapon and we still need to define the ammunition itself.
For now, we'll just give whoever the first owner of the weapon is some ammunition. Define the OnOwnerChanged-hook serverside (init.lua):

function WeaponSMG1:OnOwnerChanged(owner)
	if(self.m_bPickedUp == true or owner == nil or (owner:IsPlayer() == false and owner:IsNPC() == false)) then return end
	self.m_bPickedUp = true
	owner:AddAmmo(self.Primary.AmmoType,45)
	owner:AddAmmo(self.Secondary.AmmoType,1)
	self:RefillPrimaryClip()
end

AddAmmo adds ammunition to the owner's main ammo storage. RefillPrimaryClip refills the weapon's clip, so we don't have to reload it immediately after it was picked up.
Now let's define our actual ammo types:

Ammunition

Since ammunition types are usually shared between weapons, we won't specify them anywhere in our weapon class.
Instead, let's create a new Lua-file in "lua/autorun/" called "hl2_smg.lua" with the following content:

game.register_ammo_type("smg1",4,50.0)
game.register_ammo_type("smg1_grenade",48,240,game.DAMAGETYPE_EXPLOSION)

The first argument is the name of the ammo type, which has to be identical to what we've specified in the weapon earlier.
The second argument is the default damage of whatever uses this ammo will deal, the third represents the damage force, and the fourth (optional) argument is the damage type.

If we now equip our weapon using the "give_weapon weapon_smg1" command, we will now be able to fire it with the primary attack key.
You'll notice that the effects are still lackluster (no muzzle flash and shell ejection), so we'll do something about that in the next parts.

Tracer and Muzzle Flash

Setting up a tracer and a muzzle flash isn't difficult but requires some intermediary steps.
First, let's set up a helper-function to determine where the tracer and muzzle flash should originate from:

function WeaponSMG1:GetMuzzlePos()
	if(CLIENT == true) then
		if(self:IsInFirstPersonMode() == true) then
			local vm = ents.get_view_model()
			if(vm ~= nil) then
				local pos = vm:GetAttachment("muzzle")
				if(pos ~= nil) then
					pos = vm:LocalToWorld(pos)
					return pos
				end
			end
		end
	end
	local pos = self:GetAttachment("muzzle")
	if(pos == nil) then return self:GetPos() end
	return self:LocalToWorld(pos)
end

Depending on whether we are in first- or in third-person, and whether the local player or a different player has fired the weapon, we'll want the muzzle-flash to appear somewhere differently.
In first-person it should appear at the muzzle of the view-model and in all other cases it should appear on the muzzle of the world-model. The above function chooses which one to use:

function WeaponSMG1:GetMuzzlePos()
	if(CLIENT == true) then
		if(self:IsInFirstPersonMode() == true) then
			local vm = ents.get_view_model()
			if(vm ~= nil) then
				local pos = vm:GetAttachment("muzzle")
				if(pos ~= nil) then
					pos = vm:LocalToWorld(pos)
					return pos
				end
			end
		end
	end
	[...]
end

IsInFirstPersonMode is true if the weapon belongs to the local player, and the local player is in first-person mode (Which means the weapon is in first-person mode as well).
There is always only one view-model at a time, which we can retrieve using ents.get_view_model. Then we'll just grab the muzzle attachment position from the view-model and return that.

function WeaponSMG1:GetMuzzlePos()
	[...]
	local pos = self:GetAttachment("muzzle")
	if(pos == nil) then return self:GetPos() end
	return self:LocalToWorld(pos)
end

In all other cases we'll want the muzzle position of the world-model, which is the weapon itself.

Now we have to make a small alteration to our OnPrimaryAttack hook:

function WeaponSMG1:OnPrimaryAttack()
	[...]
	local owner = self:GetOwner()
	self.m_bulletInfo.attacker = (owner ~= nil) and owner or ents.get_null()
	self.m_bulletInfo.effectOrigin = self:GetMuzzlePos()
	self:FireBullets(self.m_bulletInfo)
	[...]
end

self.m_bulletInfo.effectOrigin is the new line, where we set the origin for our effects.
Now let's go back to our Initialize-function and set the tracerCount to 1:

function WeaponSMG1:Initialize()
	[...]
	bulletInfo.tracerCount = 1
	[...]
end

Now a tracer will appear every time the weapon is fired.
For the muzzle flash, we will need one additional hook, this time clientside (Meaning cl_init.lua).
The hook is called OnFireBullets and is invoked whenever a bullet is fired from the weapon. Here we can finally emit our muzzle flash effect:

function WeaponSMG1:OnFireBullets(bulletOrigin,bulletDir,effectOrigin)
	local ent
	if(self:IsInFirstPersonMode() == true) then ent = ents.get_view_model() end
	if(ent == nil) then ent = self end
	util.create_muzzle_flash(ent,"muzzle",Vector(0,0,10))
end

We could just use effectOrigin as origin for the muzzle flash, however it can look a bit off if the muzzle flash effect doesn't travel with the weapon when the player is rotating.
By supplying an entity and an attachment instead, the muzzle flash position will be updated every frame, resulting in a cleaner looking effect.
We'll need one more thing to finish the effects for our primary fire mode:

Shell Ejection

Whenever the weapon is fired, it would be nice to have an empty shell popping out of the weapon.
This can be done through various methods, but we'll go with the easiest one, which is a particle effect, or more specifically in this case: a giblet.
Similar to setting up our bullets, we need to define some generic info about the giblet first. To do that, let's go back to our Initialize-hook and make some small changes:

function WeaponSMG1:Initialize()
	if(CLIENT == true) then
		[...]
		local shellInfo = GibletCreateInfo()
		shellInfo.model = "weapons/shell.wmd"
		shellInfo.skin = 0
		shellInfo.scale = 1
		self.m_shellInfo = shellInfo
	end
	[...]
end

Here we set up a new GibletCreateInfo structure, which contains generic information about the shell, more specifically the model, skin and size.
Next, we need to trigger the shell ejection in our OnPrimaryAttack-hook:

function WeaponSMG1:OnPrimaryAttack()
	[...]
	self:FireBullets(self.m_bulletInfo)
	if(CLIENT == true) then
		self:EmitSound("weapon_smg1.fire1",1.0,1.0)
		self:PlayViewActivity(Animation.ACT_VM_PRIMARY_ATTACK,Entity.FPLAYANIM_RESET)
		self:EjectShell()
	else
		self:RemovePrimaryClip()
	end
	[...]
end

The EjectShell is the new line. This will a shell to spawn clientside whenever we fire the weapon.
Now we just need to define the function in cl_init.lua:

function WeaponSMG1:EjectShell()
	local pos
	local rot
	if(self:IsInFirstPersonMode() == true) then
		local vm = ents.get_view_model()
		if(vm ~= nil) then
			pos = vm:GetAttachment("eject")
			if(pos ~= nil) then pos = vm:LocalToWorld(pos) end
			rot = vm:GetRotation()
		end
	end
	if(pos == nil) then
		pos = self:GetAttachment("eject") or self:GetPos()
		pos = self:LocalToWorld(pos)
		rot = self:GetRotation()
	end
	
	local gibletInfo = self.m_shellInfo
	gibletInfo.position = pos
	gibletInfo.rotation = rot *EulerAngles(0,0,90):ToQuaternion()
	gibletInfo.velocity = rot:GetRight() *20 +rot:GetUp() *40
	gibletInfo.angularVelocity = vector.random() *math.randomf(0.0,4)
	util.create_giblet(gibletInfo)
end

The first part of this function merely determines the origin of the giblet (i.e. where to spawn it).
The second part adds the information to the GibletCreateInfo structure we have created earlier, as well as a linear and angular velocity for the shell.
Finally, util.create_giblet spawns the actual shell and causes it to disappear after a while.

With that, all of our effects are in place. However, two essential features are still missing from our weapon:

  • Reloading
  • Secondary Fire

We'll tackle these in the next sections:

Reloading

So far, when our clip would run out, the weapon would become useless, even if the player still has ammunition in reserve.
To fix that, we need to be able to reload the weapon. Luckily, this isn't too difficult.
We need two more functions in our shared.lua:

function WeaponSMG1:OnReload()
	if(self:IsReloading() or self:GetPrimaryClipSize() == self:GetMaxPrimaryClipSize()) then return end
	local owner = self:GetOwner()
	if(owner ~= nil and owner:IsPlayer() and owner:GetAmmoCount(self:GetPrimaryAmmoType()) == 0) then return end
	if(CLIENT == true) then
		self:EmitSound("weapon_smg1.reload",1.0,1.0)
		self:PlayViewActivity(Animation.ACT_VM_RELOAD)
	end
	self.m_bReloading = true
	self.m_tReload = time.cur_time() +self.ReloadDelay
end

function WeaponSMG1:IsReloading() return self.m_bReloading end

The 'OnReload'-hook is called when the owner wants to reload the weapon (Usually when the player presses the reload-button). Let's break it down a little:

function WeaponSMG1:OnReload()
	if(self:IsReloading() or self:GetPrimaryClipSize() == self:GetMaxPrimaryClipSize()) then return end
	[...]
end

If the weapon is already being reloaded, or the weapon's clip size is already at maximum, we don't want to allow reloading, so we'll just skip the function.

function WeaponSMG1:OnReload()
	[...]
	local owner = self:GetOwner()
	if(owner ~= nil and owner:IsPlayer() and owner:GetAmmoCount(self:GetPrimaryAmmoType()) == 0) then return end
	[...]
end

Next up, we'll have to check whether our owner actually has any ammo in reserve - Otherwise he can't reload either.
If we don't have an owner, we'll just allow reloading anyway.

function WeaponSMG1:OnReload()
	[...]
	if(CLIENT == true) then
		self:EmitSound("weapon_smg1.reload",1.0,1.0)
		self:PlayViewActivity(Animation.ACT_VM_RELOAD)
	end
	[...]
end

Of course we want some feedback for the player, so we'll play an animation and a sound.

function WeaponSMG1:OnReload()
	[...]
	self.m_bReloading = true
	self.m_tReload = time.cur_time() +self.ReloadDelay
	[...]
end

Finally we initiate the actual reloading. We don't want to reload the clip immediately - this would potentially allow the player to skip the reload animation and begin firing immediately after.
Instead, we'll use a time delay before we actually refill our clip, which is what the variable m_tReload is for.
We can trigger the refill in a new Think-hook, which is called every game tick:

function WeaponSMG1:Think()
	if(self:IsDeployed() == false) then return end
	if(self:IsReloading() == true and time.cur_time() >= self.m_tReload) then
		if(SERVER) then self:RefillPrimaryClip() end
		self.m_bReloading = false
	end
end

The weapon cannot be reloaded if it's not deployed, so we'll skip right there if that's the case.
Otherwise, we'll wait util the reload delay has passed, and then refill our clip using RefillPrimaryClip.
As a last step, we want to prevent the player from firing the weapon while it is reloading. To do so, we'll add this line to the beginning of the OnPrimaryAttack-hook:

function WeaponSMG1:OnPrimaryAttack()
	if(self:IsReloading() == true) then return end
	[...]
end

And with that, our reload feature is complete!

Secondary Fire Mode

The secondary fire mode for the SMG is a grenade launcher. The grenade is its own entity, which is already included in the resource pack, the weapon merely has to spawn it.
As you may have already guessed, we'll need a OnSecondaryAttack-hook for the secondary fire:

function WeaponSMG1:OnSecondaryAttack()
	if(self:IsReloading() == true) then return end
	local owner = self:GetOwner()
	if(owner ~= nil and owner:GetAmmoCount(self:GetSecondaryAmmoType()) == 0) then
		if(CLIENT == true) then
			self:EmitSound("weapon_smg1.empty",1.0,1.0)
		end
	else
		if(CLIENT == true) then
			self:EmitSound("weapon_smg1.fire_grenade_launcher",1.0,1.0)
			self:PlayViewActivity(Animation.ACT_VM_SECONDARY_ATTACK,Entity.FPLAYANIM_RESET)
		else
			if(owner ~= nil) then owner:RemoveAmmo(self:GetSecondaryAmmoType(),1) end
			self:FireGrenade()
		end
	end
	self:SetNextAttack(time.cur_time() +self.Secondary.AttackDelay)
end

There are also hooks for a tertiary attack and a fourth attack, but we won't need those here.
The first part of the function is similar to OnPrimaryAttack, but with some notable differences:

function WeaponSMG1:OnSecondaryAttack()
	[...]
	local owner = self:GetOwner()
	if(owner ~= nil and owner:GetAmmoCount(self:GetSecondaryAmmoType()) == 0) then
		if(CLIENT == true) then
			self:EmitSound("weapon_smg1.empty",1.0,1.0)
		end
		[...]
	end
	[...]
end

In OnPrimaryAttack we had to check if there was enough ammo in the clip before firing, however our secondary attack will directly use the owner's ammunition instead (Meaning it doesn't have a clip).
So all we need to do is check if the player's ammo of the secondary ammo type isn't empty.

function WeaponSMG1:OnSecondaryAttack()
	[...]
	local owner = self:GetOwner()
	if(owner ~= nil and owner:GetAmmoCount(self:GetSecondaryAmmoType()) == 0) then
		[...]
	else
		[...]
		if(CLIENT == true) then
			self:EmitSound("weapon_smg1.fire_grenade_launcher",1.0,1.0)
			self:PlayViewActivity(Animation.ACT_VM_SECONDARY_ATTACK,Entity.FPLAYANIM_RESET)
		else
			if(owner ~= nil) then owner:RemoveAmmo(self:GetSecondaryAmmoType(),1) end
			self:FireGrenade()
		end
	end
	[...]
end

We're also using RemoveAmmo instead of RemovePrimaryClip, and since we're not firing a bullet, we have a call to FireGrenade, which we still have to define.
We only need the code for that function serverside, so we'll add it to the init.lua:

function WeaponSMG1:FireGrenade()
	local owner = self:GetOwner()
	if(owner == nil) then return end
	local pos = self:GetProjectileOrigin()
	local entGrenade = ents.create("proj_smg1_grenade")
	if(entGrenade ~= nil) then
		local dir
		local rot
		if(owner:IsPlayer()) then
			rot = owner:GetViewRotation()
			dir = owner:GetViewForward() +owner:GetViewUp() *0.1
			dir:Normalize()
		else
			rot = owner:GetRotation()
			dir = owner:GetForward()
		end
		entGrenade:DisableCollisions(owner)
		entGrenade:DisableCollisions(self)
		entGrenade:SetAttacker(owner)
		entGrenade:SetPos(pos +dir *20.0)
		entGrenade:SetRotation(rot)
		entGrenade:Spawn()
		entGrenade:SetVelocity(dir *1000)
		entGrenade:SetLocalAngularVelocity(Vector(10,0,0))
	end
end

function WeaponSMG1:GetProjectileOrigin()
	local owner = self:GetOwner()
	if(util.is_valid(owner) == true) then
		local pos = owner:GetShootPos()
		if(owner:IsPlayer() == true) then
			pos = pos +owner:GetViewRight() *10 -owner:GetViewUp() *9
		end
		return pos
	end
	return self:GetPos()
end

Explanation:

function WeaponSMG1:FireGrenade()
	[...]
	local pos = self:GetProjectileOrigin()
	[...]
end

function WeaponSMG1:GetProjectileOrigin()
	local owner = self:GetOwner()
	if(util.is_valid(owner) == true) then
		local pos = owner:GetShootPos()
		if(owner:IsPlayer() == true) then
			pos = pos +owner:GetViewRight() *10 -owner:GetViewUp() *9
		end
		return pos
	end
	return self:GetPos()
end

The projectile origin is the position at which the grenade will be spawned. GetShootPos represents the center of the screen of the player. To move the grenade closer to the weapon, we have to move it slightly to the right and slightly up (Relative to the player's view), where GetViewRight and GetViewUp come in handy.

function WeaponSMG1:FireGrenade()
	[...]
	local entGrenade = ents.create("proj_smg1_grenade")
	if(entGrenade ~= nil) then
		[...]
	end
end

As mentioned, the entity for the grenade already exists in the resource package. Its class is "proj_smg1_grenade", you can check out "lua/entities/proj_smg1_grenade/" to look at its code, but it won't be described here.
The above call will attempt to create the entity. This can fail, so we have to make sure the result isn't nil.

function WeaponSMG1:FireGrenade()
	[...]
	if(entGrenade ~= nil) then
		local dir
		local rot
		if(owner:IsPlayer()) then
			rot = owner:GetViewRotation()
			dir = owner:GetViewForward() +owner:GetViewUp() *0.1
			dir:Normalize()
		else
			rot = owner:GetRotation()
			dir = owner:GetForward() +owner:GetUp() *0.1
		end
		[...]
	end
end

dir in the above code describes the direction in which the grenade should be launched. It should be launched away from the player (GetViewForward), but also slightly upwards (GetViewUp), otherwise the grenade would fall off too quickly.

function WeaponSMG1:FireGrenade()
	[...]
	if(entGrenade ~= nil) then
		[...]
		entGrenade:DisableCollisions(owner)
		entGrenade:DisableCollisions(self)
		entGrenade:SetAttacker(owner)
		[...]
	end
end

We also want to make sure the grenade doesn't collide with the weapon's owner, or the weapon itself, so we'll disable the collisions between the entities.
SetAttacker is required, so the grenade knows who initiated the attack.

function WeaponSMG1:FireGrenade()
	[...]
	if(entGrenade ~= nil) then
		[...]
		entGrenade:SetPos(pos +dir *20.0)
		entGrenade:SetRotation(rot)
		entGrenade:Spawn()
		entGrenade:SetVelocity(dir *1000)
		entGrenade:SetLocalAngularVelocity(Vector(10,0,0))
	end
end

Now we just need to apply everything to the grenade entity.
The physics component of the grenade doesn't exist until Spawn is called, which is why we're calling SetVelocity and SetLocalAngularVelocity after Spawn.
And that's it, using the secondary attack button should now launch a grenade (If enough ammunition is available)!

Sandbox Integration

Giving the weapon to the player via console is rather cumbersome, luckily there's a better way: Adding it to the sandbox spawn-menu.
To do so, open up the "hl2_smg.lua" file you have created earlier, and make the following changes:

if(CLIENT == true) then locale.load("weapon_smg1.txt") end
game.add_callback("OnGameModeInitialized",function(gm)
	if(gm.RegisterEntity == nil) then return end
	gm:RegisterEntity(GMSandbox.ENTITY_CATEGORY_WEAPON,"weapon_smg1","hl2_smg1")
end)

game.register_ammo_type("smg1",4,50.0)
game.register_ammo_type("smg1_grenade",48,240,game.DAMAGETYPE_EXPLOSION)

The first line loads the localization file for the addon, which contains some names and translations for the SMG. It's already included in the resource pack.
Next, we have to register the weapon with the gamemode, if the gamemode allows it. That's what we need the OnGameModeInitialized game callback for:

[...]
game.add_callback("OnGameModeInitialized",function(gm)
	if(gm.RegisterEntity == nil) then return end
	gm:RegisterEntity(GMSandbox.ENTITY_CATEGORY_WEAPON,"weapon_smg1","hl2_smg1")
end)
[...]

If the gamemode doesn't have a RegisterEntity method, we're not in a sandbox or sandbox-related gamemode, so we'll just skip the rest.
Otherwise, we'll add the weapon to the spawn menu using the RegisterEntity-call. The first argument specifies the category in which the entity should appear, the second category is the class name of the weapon, and the third argument is its localization string as defined in the localization file.
The weapon should now appear in the sandbox spawn-menu (You can bind a key to it in the options), and you should be able to spawn it on the ground and pick it up with the use-key.
Included in the resource pack are also two ammo-entities, which you can add to the sandbox menu the same way:

if(CLIENT == true) then locale.load("weapon_smg1.txt") end
game.add_callback("OnGameModeInitialized",function(gm)
	if(gm.RegisterEntity == nil) then return end
	gm:RegisterEntity(GMSandbox.ENTITY_CATEGORY_WEAPON,"weapon_smg1","hl2_smg1")
	gm:RegisterEntity(GMSandbox.ENTITY_CATEGORY_ENTITY,"item_ammo_smg1_grenade","hl2_ammo_smg1_grenade")
	gm:RegisterEntity(GMSandbox.ENTITY_CATEGORY_ENTITY,"item_ammo_smg1","hl2_ammo_smg1")
end)

game.register_ammo_type("smg1",4,50.0)
game.register_ammo_type("smg1_grenade",48,240,game.DAMAGETYPE_EXPLOSION)


You can get the full addon including the source code here: http://mods.sciolyte.com/index.php?mode=upload&uploadid=cfbd4710b6b81100d3a7579cd80eac8f