Authoring a weapon entity
It should come as no surprise that Source has very robust weapon systems. This article covers the steps required to create both hitscan and projectile weapons, and how to correctly set them up for use in multiplayer.
Overview
- Most properties of a gun can be configured with a script file
- If all you want to do is spit out bullets at a constant rate, the amount of actual programming required is minimal.
- Weaponsfire can be predicted by the client and lag-compensated by the server
- This is normally only done with hitscan bullets, as rockets, grenades and other visible projectiles materialising out of thin air on observers' computers is hard to pass off!
- The viewmodel is a separate entity
- It is created by the player and shared between all weapons.
- There is a wide selection of weapons in the SDK to learn from
- Valve provide source code from both singleplayer and multiplayer games.
Weapon scripts
A weapon script defines most of the properties of a weapon, including its models, ammunition, sounds, clip size, and UI name. Using, extending and encrypting weapon scripts is covered in their article.
A weapon script will automatically be loaded for any weapon with an assigned classname. To easily precache script-defined resources, call PRECACHE_WEAPON_REGISTER(classname)
- no quotemarks around the classname.
Entry points
There are four functions on a weapon which are called each frame by the owning player entity:
ItemPreFrame()
- Called before the player has moved. In Episode Two, decides whether now is a good time to display a HUD hint on weapon usage.
ItemHolsterFrame()
- Called before the player has moved, if the weapon is not active. Does nothing by default.
ItemBusyFrame()
- Called after the player has moved, if the player cannot fire. Does nothing by default.
ItemPostFrame()
- Called after the player has moved. The most important function, as it leads to one of the following:
PrimaryAttack()
SecondaryAttack()
HandleFireOnEmpty()
Reload()
WeaponIdle()
Shooting
A gun will typically shoot when the +attack key is active and (m_flNextPrimaryAttack <= gpGlobals->curtime)
. For secondary fire, +attack2 and m_flNextSecondaryAttack
are checked instead.
Bullets
Hitscan bullets are fired by passing a FireBulletsInfo_t
object to GetOwner()->FireBullets()
. In Source, guns really don't kill people!
If all you want is a primary fire attack that spits out a constant stream of bullets (no matter whether they are 2 or 0.2 seconds apart), CBaseCombatWeapon
is already up to the job and you only need write two functions:
float GetFireRate()
- Seconds between each bullet.
const Vector& GetBulletSpread()
- Cone within which bullets will be randomly spread. Use the
VECTOR_CONE_*
#defines to avoid doing the maths yourself.
Skip on to the networking section if this fits your bill; effects will be handled for you. Otherwise, see FireBulletsInfo_t
for details on setting up your own hitscan attack.
Projectiles
Rockets, grenades and other projectiles are entities in their own right, which exist independently of the weapon they came from. The weapon should spawn them with Create()
:
CBaseEntity::Create( "my_projectile", GetOwner()->Weapon_ShootPosition(), GetOwner()->EyeAngles(), GetOwnerEntity() );
Don't lag compensate or predict this call - but do predict viewmodel animation, ammo depletion and reloading.
Further reading:
- Projectiles (for projectile design issues)
- Authoring a Model Entity (for creating QPhysics entities)
CPhysicsProp
(for creating VPhysics entities)
Effects
Networking
Sample weapon
[Todo]