Chair Throwing SWEP

From GMod Wiki

Jump to: navigation, search
Lua: Chair Throwing SWEP tutorial
Page white text.png Description:Tutorial covering making a SWEP weapon that spawns and launches chair props.
link=User: Sam Douglas Original Author: Sam Douglas
Calendar.png Created:5 December 2006
Announcing his future employment at Google was Kleiner's downfall


This tutorial covers writing a simple SWEP weapon. It is quite similar to Garry's Example SWEP. This tutorial assumes you know the basics of programming in Lua; if not I suggest you read up on that first.

This tutorial is still a bit rough; so please feel free to make improvements.

Contents

File organization

Garry's Mod scripted weapons must be installed on the server in order for them to be used. The necessary files will be copied onto clients as they connect to your server. Scripted weapons and entities are installed on a per-game mode basis. The base directory for game mode data is:

<Steam directory>/steamapps/<username>/garrysmod/garrysmod/gamemodes
    Note: if you cannot find the folders. Create them!


Inside it is a directory for each game mode that is installed, containing Lua code, models and other information. The "base" game mode is common across all game modes.

For this SWEP, we will only make it available in the sandbox game mode. Make a new directory for the SWEP in the:

sandbox/entities/weapons

Now create a new directory called something like "chair_throwing_gun". We will put all of the SWEP's files inside this directory.

Skeleton code

The basic skeleton of this SWEP consists of 3 files; init.lua which is loaded by the server when you start a game; cl_init.lua which is sent to the client when they connect to the server, and shared.lua which is also sent to the client when they connect.

init.lua

 
AddCSLuaFile ("cl_init.lua");
AddCSLuaFile ("shared.lua");
 
include ("shared.lua");
 
SWEP.Weight = 5;
SWEP.AutoSwitchTo = false;
SWEP.AutoSwitchFrom = false;
 

The AddCSLuaFile() function tells the server to send the specified file to the client when they connect. The include() function works much like the C preprocessor #include; it takes the contents of the specified file and inserts it into the current script at the location where include was encountered. It would be as if you copy-pasted the contents of shared.lua into init.lua.

SWEP.Weight sets the weight of the SWEP, for example, when it is dropped by an NPC. SWEP.AutoSwitchTo sets whether the weapon will automatically be selected when picked up, and SWEP.AutoSwitchFrom sets whether another weapon will be auto selected when picked up, after having this SWEP selected.

cl_init.lua

 
include ("shared.lua");
 
SWEP.PrintName = "Chair throwing gun";
SWEP.Slot = 3;
SWEP.SlotPos = 1;
SWEP.DrawAmmo = false;
SWEP.DrawCrosshair = false;
 

This file sets client side weapon settings, such as the slot (where it appears in the inventory).

alternatives

An easier way to do this (thanks to garry), without creating two files, is to make a file called "shared.lua", and have the stuff in init.lua inside a "if (SERVER)" function, and the cl_init.lua stuff inside the "if (CLIENT)" function.

shared.lua:

 
if (SERVER) then --the init.lua stuff goes in here
 
 
   AddCSLuaFile ("shared.lua");
 
 
   SWEP.Weight = 5;
   SWEP.AutoSwitchTo = false;
   SWEP.AutoSwitchFrom = false;
 
end
 
if (CLIENT) then --the cl_init.lua stuff goes in here
 
 
   SWEP.PrintName = "Chair throwing gun";
   SWEP.Slot = 3;
   SWEP.SlotPos = 1;
   SWEP.DrawAmmo = false;
   SWEP.DrawCrosshair = false;
 
end
 
--Yay!
 

Single-Line Comments

if you know what comments in lua are. Skip to the next section!

Notice how I put two slashes in front of the green text. That is called a single-line comment, and Gmod ignores anything after that, on the line. You can add comments almost anywhere in the code, as long as you dont put it in the middle of a line, like this:

 
SWEP.PrintName --This Prints the name  = "Chair throwing gun"
 

My comment completely got rid of the = "Chair throwing gun" part, so Gmod ignored it! NOT GOOD! This is how you do it properly.

 
 
SWEP.PrintName = "Chair throwing gun" --This prints the name
 
 

See? the "Chair throwing gun" part isn't green anymore, thus is not ignored by Gmod.

Setting weapon properties

Inside the shared.lua file; we define numerous properties and functions that Garry's Mod will look for when using the SWEP weapon; for example when the client asks to fire the weapon, Garry's Mod will execute the PrimaryAttack() function.

shared.lua

We will incrementally build up the shared.lua file until we have a functional weapon.

 
SWEP.Author = "Sam Douglas";
SWEP.Contact = "[email protected]";
SWEP.Purpose = "Assists in throwing chairs";
SWEP.Instructions = "Left click to throw an office chair; right click to throw a wooden chair";
SWEP.Category = "Prop Launchers"
 

This information is used by Garry's Mod when creating the item in the Weapons tab of the Spawn menu, and when selecting the gun in your inventory.

SWEP.Category tells Garry's Mod to put the gun in a certain category. For example, you can see all of the counter strike weapons in the Counter-Strike category, and others in the weapons tab.

 
SWEP.Spawnable = true;
SWEP.AdminSpawnable = true;
 

This controls who can spawn the weapon. If the weapon is Spawnable, then anyone can spawn it; but if it is only admin spawnable, then only a server admin can spawn it. This is good if you want to make a SWEP that would be too dangerous in the hands of a Mingebag (i.e. Garry's manhack gun).

 
SWEP.ViewModel = "models/weapons/v_pistol.mdl";
SWEP.WorldModel = "models/weapons/w_pistol.mdl";
 

This allows you to select the model used to display the weapon. The View model is the one you see in first person view (the hands holding the gun), whereas the world model is what other people see the weapon as.

 
SWEP.Primary.ClipSize = -1;
SWEP.Primary.DefaultClip = -1;
SWEP.Primary.Automatic = false;
SWEP.Primary.Ammo = "none";
 
SWEP.Secondary.ClipSize = -1;
SWEP.Secondary.DefaultClip = -1;
SWEP.Secondary.Automatic = false;
SWEP.Secondary.Ammo = "none";
 

This sets information about the weapon clip size and stuff. We will not use these, as this weapon is designed to shoot chairs, not bullets. This is why we put a -1. If we were going to make a bullet-shooting weapon, the ClipSize would determine how many bullets are in each clip. For example, the pistol from Half Life 2 has a 18 bullet ClipSize. The DefaultClip is exactly how many extra bullets the gun has. So, if you wanted a weapon that when you started it out, it had 1337 extra bullets, you would put 1337. The Automatic part determines if you hold down the trigger, will it continue firing, or will it only shoot once? If it is true, it will keep firing like an SMG, but if it is false, it will only fire once, like a pistol. Ammo is determining what kind of ammo the gun is using. If it uses pistol ammo, put pistol, etc, etc. There are many other kinds of functions for regular guns: here is an example for primary fire for an average gun:

 
--WE ARE NOT USING THIS FOR THE CHAIR GUN!!!
--WE ARE NOT USING THIS FOR THE CHAIR GUN!!!
--WE ARE NOT USING THIS FOR THE CHAIR GUN!!!
--WE ARE NOT USING THIS FOR THE CHAIR GUN!!!
SWEP.Primary.Recoil		= 1.5 --how much the gun goes up when you shoot it
SWEP.Primary.Damage		= 14 --how much damage it does per shot
SWEP.Primary.NumShots		= 1 --Number of bullets that come out per shot, set to more than 1 if it is a shotgun basically
SWEP.Primary.Cone		= 0.01 --This is how accurate your gun is, a cone of 0 is completely accurate, a cone of 1 is horribly accurate
SWEP.Primary.ClipSize		= 42 --Explained above
SWEP.Primary.Delay		= 0.08 --delay in seconds between each shot
SWEP.Primary.DefaultClip		= 1337 --explained above
SWEP.Primary.Automatic		= false --explained above
SWEP.Primary.Ammo		= "smg1" --explained above
 

Implementation

This is where the interesting stuff happens. We define functions in the SWEP table inside shared.lua which are called by Garry's Mod when something significant happens.

The significant functions that I know of are:


Reload() 
   This function is run when the weapon is asked to reload.
Think() 
   This function is run every server tick (very frequently) and the time taken to execute any code in the Think function should be taken into account when writing the SWEP.
PrimaryAttack() 
   This is run whenever the primary attack on the weapon is done(Mouse1).
SecondaryAttack() 
   Done whenever the secondary attack is done(Mouse2).

All of these functions are implemented as methods inside the table called SWEP.

Local variables

Before writing our functions; we will create a local variable and load up the sound of the gun firing into it. This saves us from repeatedly loading the sound whenever the attack functions are called.

I was too lazy to find a more appropriate sound, so I shamelessly used the one straight from Garry's example SWEP (Manhack gun).

 
local ShootSound = Sound ("Metal.SawbladeStick");
 

Functions

In this SWEP, we do not use the Think or Reload functions, so we will provide an empty implementation of them. We do, however use Initialize

 
function SWEP:Reload()
end
 
function SWEP:Think()
end
 
function SWEP:Initialize()
       self:SetWeaponHoldType( self.HoldType ) --Sets the weapon hold type to    
                                               --the swep's hold type variable.
end
 

Multi-Line Comments

Skip this and don't waste your time if you know what multi-line comment is, if not read carefully.


Also, did you notice those /* things? That is called a multi-line comment. It is a lot more useful that a single // line comment because you don't have to keep redoing it for each line. Here is an example:

 
/*
Garry's Mod is completely ignoring this. 
Even if I make a new line, This block of text is still green.
I'm so happy!
 
*/
 
 

Compared to:

 
 
//Garry's Mod is completely ignoring this. 
When I make a new line, however, this block of text is not ignored by Garry's Mod,
thus messing my code up. :(
 

Attack functions

For this weapon, both the primary and secondary fire do more or less the same thing but using different models. For this reason; we will actually create a function called throw_attack() which is passed the name of the model to throw. This avoids duplicating the same code (and potentially bugs) and is good programming practice. It also makes the chair throwing SWEP a lot easier to modify to throw any model you want.

This function will be created as a method inside the SWEP table, so code inside it will be identical to code you would use in the normal PrimaryAttack() and SecondaryAttack() functions.

This function is executed on both the client and the server; however the function ends partway through if it is not running on the server. This means that some stuff (like making noises and effects) are done client side, because they do not really need to wait for them to come from the server.

 
/* Spawn and throw the specified model */
function SWEP:throw_attack (model_file)
	//Get an eye trace. This basically finds out where the shot hit
	//This SWEP makes very little use of the trace, except to calculate
	//the amount of force to apply to the object to throw it.
	local tr = self.Owner:GetEyeTrace();
 
	//We now make some shooting noises and effects using the sound we
	//loaded up earlier
	self:EmitSound (ShootSound);
	self.BaseClass.ShootEffects (self);
 
	//We now exit if this function is not running on the server
	if (!SERVER) then return end;
 
	//The next task is to create a physics entity based on the supplied model.
	local ent = ents.Create ("prop_physics");
	ent:SetModel (model_file);
 
	//Set the initial position of the object. This might need some fine tuning; but it
	//seems to work for the models I have tried
	ent:SetPos (self.Owner:EyePos() + (self.Owner:GetAimVector() * 16));
	ent:SetAngles (self.Owner:EyeAngles());
	ent:Spawn();
 
	//Now we need to get the physics object for our entity so we can apply a force to it
	local phys = ent:GetPhysicsObject();
 
	//Time to apply the force. My method for doing this was almost entirely empirical 
	//and it seems to work fairly intuitively with chairs.
	local shot_length = tr.HitPos:Length();
	phys:ApplyForceCenter (self.Owner:GetAimVector():GetNormalized() * math.pow (shot_length, 3));
 
	//Now for the all important part of adding the spawned objects to the undo and cleanup data.
	cleanup.Add (self.Owner, "props", ent);
 
	undo.Create ("Thrown chair");
	undo.AddEntity (ent);
	undo.SetPlayer (self.Owner);
	undo.Finish();
end
 

The comments in that code should explain most of it. We now have to make PrimaryAttack() and SecondaryAttack() call our throw_attack() function.

 
-- Throw an office chair on primary attack
function SWEP:PrimaryAttack()
	//Call the throw attack function
	self:throw_attack ("models/props/cs_office/Chair_office.mdl");
end
 
--Throw a wooden chair on secondary attack
function SWEP:SecondaryAttack()
	//Call the throw attack function
	self:throw_attack ("models/props_c17/FurnitureChair001a.mdl");
end
 

If you do not have a copy of CounterStrike: Source or HalfLife 2 you will have to choose different models. The model names for props in the spawn menu can currently be found out by hovering over their icon.

Completed SWEP

Included for convenience is a completed version of the source code. Only shared.lua is provided here; cl_init.lua and init.lua are provided intact in the Skeleton Code section. You don't need them, as there are the SERVER and CLIENT functions inside the code below. I just wanted you to know how it works.

shared.lua

 
if (SERVER) then --the init.lua stuff goes in here
 
 
   AddCSLuaFile ("shared.lua");
 
 
   SWEP.Weight = 5;
   SWEP.AutoSwitchTo = false;
   SWEP.AutoSwitchFrom = false;
 
end
 
if (CLIENT) then --the cl_init.lua stuff goes in here
 
 
   SWEP.PrintName = "Chair throwing gun";
   SWEP.Slot = 3;
   SWEP.SlotPos = 1;
   SWEP.DrawAmmo = false;
   SWEP.DrawCrosshair = false;
 
end
 
 
SWEP.Author = "Sam Douglas";
SWEP.Contact = "[email protected]";
SWEP.Purpose = "Assists in throwing chairs";
SWEP.Instructions = "Left click to throw an office chair; right click to throw a wooden chair";
SWEP.Category = "Prop Launchers"
 
SWEP.Spawnable = true;
SWEP.AdminSpawnable = true;
 
SWEP.ViewModel = "models/weapons/v_pistol.mdl";
SWEP.WorldModel = "models/weapons/w_pistol.mdl";
 
SWEP.Primary.ClipSize = -1;
SWEP.Primary.DefaultClip = -1;
SWEP.Primary.Automatic = false;
SWEP.Primary.Ammo = "none";
 
SWEP.Secondary.ClipSize = -1;
SWEP.Secondary.DefaultClip = -1;
SWEP.Secondary.Automatic = false;
SWEP.Secondary.Ammo = "none";
 
// This is a single line comment!
 
/* 
this is a 
MULTI-LINE
comment!
*/
 
local ShootSound = Sound ("Metal.SawbladeStick");
 
/* Both Reload() and Think() are not needed by this SWEP so we
 * will provide empty functions for them
 */
function SWEP:Reload()
end
 
function SWEP:Think()
end
 
/* Spawn and throw the specified model */
function SWEP:throw_attack (model_file)
	//Get an eye trace. This basically finds out where the shot hit
	//This SWEP makes very little use of the trace, except to calculate
	//the amount of force to apply to the object to throw it.
	local tr = self.Owner:GetEyeTrace();
 
	//We now make some shooting noises and effects using the sound we
	//loaded up earlier
	self:EmitSound (ShootSound);
	self.BaseClass.ShootEffects (self);
 
	//We now exit if this function is not running on the server
	if (!SERVER) then return end;
 
	//The next task is to create a physics entity based on the supplied model.
	local ent = ents.Create ("prop_physics");
	ent:SetModel (model_file);
 
	//Set the initial position of the object. This might need some fine tuning; but it
	//seems to work for the models I have tried
	ent:SetPos (self.Owner:EyePos() + (self.Owner:GetAimVector() * 16));
	ent:SetAngles (self.Owner:EyeAngles());
	ent:Spawn();
 
	//Now we need to get the physics object for our entity so we can apply a force to it
	local phys = ent:GetPhysicsObject();
 
	//Time to apply the force. My method for doing this was almost entirely empirical 
	//and it seems to work fairly intuitively with chairs.
	local shot_length = tr.HitPos:Length();
	phys:ApplyForceCenter (self.Owner:GetAimVector():GetNormalized() *  math.pow(shot_length, 3));
 
	//Now for the all important part of adding the spawned objects to the undo and cleanup data.
	cleanup.Add (self.Owner, "props", ent);
 
	undo.Create ("Thrown chair");
	undo.AddEntity (ent);
	undo.SetPlayer (self.Owner);
	undo.Finish();
end
 
/* Throw an office chair on primary attack */
function SWEP:PrimaryAttack()
	//Call the throw attack function
	self:throw_attack ("models/props/cs_office/Chair_office.mdl");
end
 
/* Throw a wooden chair on secondary attack */
function SWEP:SecondaryAttack()
	//Call the throw attack function
	self:throw_attack ("models/props_c17/FurnitureChair001a.mdl");
end
 

Now if you start a server/single player game in Sandbox mode, the Chair throwing gun should be available under the "Weapons" tab of the spawn menu.

Personal tools
Namespaces
Variants
Actions
Navigation
Lua Scripting
Functions
Hooks
Toolbox