Garry's Mod

Garry's Mod

Not enough ratings
How to make a LVS Wheeled Vehicle
By 8Z
Turn any prop into a working LVS vehicle! As long as it's got wheels.
   
Award
Favorite
Favorited
Unfavorite
Foreword
Hi, I'm 8Z, and I made addons for Garry's Mod sometimes.

Recently, I got interested in LVS base after seeing its new ground vehicle features, and learned to make addons for it myself. Since there was not any publicly available resources to help me through this process, I wanted to write this guide and help others to work with the base. It's surprisingly simple!

Here's a link to my vehicles!
https://steamcommunity.com/sharedfiles/filedetails/?id=3112859258

Important Caveats!
1. For the sake of brevity, I will assume you have a basic understanding of Source model formats, Lua syntax and Blender.
If this is not you and you are starting from zero, you may want to look into additional resources and start with simpler projects to get a hang of things.

2. This guide only covers wheeled ground vehicles. I have not yet worked with helicopters or planes, and cannot offer guidance specific to those vehicles. However, the fundamentals are similar.

Additional Resources

Garry's Mod Wiki[wiki.facepunch.com]. API documentation, but also has some beginner guides for learning Lua. Refer to this CONSTANTLY. I know I do.

Valve Developer Wiki for documentation of various Source formats (VMT, VTF, QC, MDL etc.)

LVS Base GitHub repository[github.com]. The function list may come in handy.

LVS Cars GitHub respository[github.com]. Many ground vehicle specific functions are in the sub-bases here.
Prerequisites
You will need various software to work with models and code. Here are the ones I personally use, and the guide will assume you use them as well. Don't worry, it's all free.

Garry's Mod Addons
Extended Spawnmenu lets you view assets from unpacked addons in the addons folder. Useful for spawning and checking your models.

Easy Entity Inspector lets you check the attachments, bones, and poseparameters of a model in-game.

Model/Texture Tools
Crowbar is a one-stop shop for various Source asset tools. It's commonly used for decompiling and compiling models, but can also unpack GMA/VPK files, open HLMV, and even upload addons. You'll have to configure your game path after installing.
https://steamcommunity.com/groups/CrowbarTool

Blender: 3D Modelling and animation software to modify and export models, and create animations. I'm on Version 3.6.
https://www.blender.org/download/

Blender Source Tools: A plugin for Blender that lets you export a model into Source engine formats.
https://developer.valvesoftware.com/wiki/Blender_Source_Tools

VTFEdit: A software for converting textures from and to Source engine formats.
https://developer.valvesoftware.com/wiki/VTFEdit

Text Editor
Visual Studio Code to edit Lua code and KeyValue format files. Any text editor will work, but I recommend VSCode as it has good plugins for every format you'll need to work with.
https://code.visualstudio.com/Download

GLua Enhanced: Code highlighting. Massive quality of life improvement.
https://marketplace.visualstudio.com/items?itemName=venner.vscode-glua-enhanced

GLuaLint: This will highlight syntax errors in your Lua code. Requires additional setup! Follow the instructions in the GitHub repository, then install the extension.
https://github.com/FPtje/GLuaFixer
https://marketplace.visualstudio.com/items?itemName=goz3rr.vscode-glualint

Valve KeyValue Files Support: Highlighting for QC and VMT formats. Not too important, but good to have.
https://marketplace.visualstudio.com/items?itemName=GEEKiDoS.vdf
Part 0: Models, Textures And You
You can skip this section if you know what you're doing.

File Structure
When a Source game boots, all of your assets will exist within the same hierarchy, regardless of where the asset came from. For example:

A model file exists on the file system at garrysmod/addons/my_addon/models/my_name/car.mdl.
In the game, the model path becomes models/my_name/car.mdl.

When referring to models and materials, assume everything is under one big folder.

This also means you must name your assets something distinct to avoid conflicting with other files.
A common approach is to put the model in a folder with your name on it.

Models
Source models are kind of funky, and it's best to explain it before you get greased up.

Before compiling, your model will look like a bunch of .smd files and one .qc file.

SMD files contain vertex info or animation info. When you export a model, it will become one SMD file. When you export an animation, it will also become one SMD file, usually in the anims folder.
These are technically text files, but you should not be editing them by hand. They're usually generated by Blender.

The QC file contain model information, such as the model name, what bodygroups it has, what sequences it has, etc. One QC file is one model.
This is an important text file you will be editing a lot.

After compiling, your model will be a MDL file and a bunch of other files. You cannot edit a compiled model - you must decompile it with Crowbar first.

The path of the model is important. It MUST be in the same path as specified in the QC file.

Textures
A texture in game consists of one .vmt file and one or more .vtf files.

VTF files are textures. You cannot edit VTF files directly - Use VTFEdit to convert it into something else.

A VMT file contains material data. It determines how the textures are rendered. This is the file you should refer to on your model.
This is a text file you can and should edit.

Using Textures on a Model
A common problem new modders encounter is they don't know how to set up textures for their model. There are several things that must be exactly correct for a texture to work!

  • The mesh in the SMD file must have materials assigned to them. The name of the material is important!
  • The model QC file must contain one or more $cdmaterials definitions. Each definition is a path to a materials directory.
  • There must be a VMT file with a name matching the one in the SMD file, in the same folder as one specified in the QC file.
  • The VMT file must refer to one or more VTF files in the materials folder. They don't have to be in the same folder.

For a practical example, let's look at the setup for my Eland models.
In Blender

Note the material name veh_mil_eland90.

QC file
$modelname "8z/lvs/eland.mdl"
$cdmaterials "models\8z\lvs\eland\"
// Some other stuff

This creates the model models/8z/lvs/eland.mdl, and looks for materials in materials/models/8z/lvs/eland.
Note that the root folders, models and materials, are omitted.

VMT file
// materials/models/8z/lvs/eland/veh_mil_eland90
"VertexlitGeneric" {
"$basetexture" "models\8z\lvs\eland\veh_mil_eland90_C"
"$bumpmap" "models\8z\lvs\eland\veh_mil_eland90_N"
// Other stuff
}
The name of the file must match the name of the material in Blender and the SMD file.
The path of the file must match one of the paths in the QC file.

VTF files

Note the folder path.
Part 1: Model Work
Let's finally get to the meaty part of the guide. Hopefully, you've already found a model you would like to make into a vehicle. If not, look for one on the Workshop.

Before you start, create a folder in garrysmod/addon. This will be your addon folder containing compiled models, textures, and Lua code. You can also put your workfolder here for organization, but you cannot upload that folder to the Workshop.


Decompile your model, and import all of its bodygroups into Blender. Make a save and save often, especially before exporting anything.

Now that you have the model in your editor, you can start making whatever changes you want, but keep everything below in mind.

Editing the Model
Blender operates within one of several modes, the ones most relevant to us are Object mode, Edit mode, and Pose mode.

When making changes to the mesh, stay in Edit mode and do not touch object transforms. Object transforms do not export properly. If you made changes to the transform, apply them in the menu bar with Object -> Apply -> All Transforms.

For more details: https://developer.valvesoftware.com/wiki/Modeling_props_with_Blender

Bodygroups/Collections
A collection in Blender is a folder that can contain objects. Blender Source Tools exports each collection as one SMD file, with the name of the collection being the collection name.

If the model has parts that are optional or are variants (e.g. different turrets, decoration), they should be in a separate collection.

If you have nesting collections, only the deepest collection will be exported. This is useful for organization.

Armature
If your model has moving parts like doors and steering wheels, you will need multiple bones so that each part can move independently. Hopefully the model port you are using already has that set up. If not, you'll have to do that yourself.

One model should only have one armature. Furthermore, make sure each object has an Armature modifier linked to your one armature.

For more details: https://developer.valvesoftware.com/wiki/Animation_in_Blender

Collision Mesh
You will need a collision mesh so that your vehicle can collide with things. A model can only have one collision mesh.

If you don't want to make your own, simply provide your model's body as the collision mesh, and a mesh will be generated for you when compiling. The generated result usually isn't very good though.

Collision meshes must be concave, and must be shaded smooth.They should have as few vertices as possible to preserve performance.
For more details: https://developer.valvesoftware.com/wiki/Collision_mesh

Wheels
In LVS, wheeled vehicles are typically set up with visible wheels as separate entities.

Your model should not contain wheels. If it does, separate them into a different collection and do not compile them into the model.

You will need to compile a separate model for the wheel if one doesn't exist already. The origin of the wheel should be at the spot where it attaches to the axles.

Tank treads and such should stay on the model, but this guide will not cover them.

Idle Animation
All models need at least an idle animation. For vehicles, you just need a single frame where everything is sitting still normally.

Switch to the Animation tab. Select your armature and go into Pose mode. In the Dope Sheet window, switch to Action Editor, create a new animation, and call it "idle". Drag the seeker to frame 0.

Select all bones by hovering over the viewport and pressing A. Then, pres I -> Location and Rotation. This creates a keyframe for all bones at their neutral position.

That's all! Just export this animation and you're ready to compile the model.
Part 1.1: Textures
If you already have textures from your model port, all you need to do is copy it over to your addon folder, and modify the VMT files.

By convention, your material path should be the same as your model. For example:

Model path: models/8z/lvs/eland.mdl
Material directory: materials/models/8z/lvs/eland/
In VMT file: "$basetexture" "models\8z\lvs\eland\veh_mil_eland90_C"

If your assets came from a non-Source source, you will have to use VTFEdit to convert them to VTF files, and then create VMT files for them. The specifics is beyond the scope of this guide, but this link may help: https://developer.valvesoftware.com/wiki/Creating_a_Material
Part 2: QC Configuration
Now that you have a bunch of meshes, you'll need a QC file to actually compile it. If you got a ported model, you can start with a copy of it as the baseline.

QC Setup
QC files follow Valve's KeyValues format. The dollar sign denotes a key (like $modelname), followed by a space, then the value (usually in double quotes). Two slashes (//) indicate the line is a comment.

For more information: https://developer.valvesoftware.com/wiki/KeyValues

The following is a non-exhaustive list of keys you need for a basic vehicle model.

$modelname
Name and path of the model. Do not include the root folder (models).
$modelname "8z/lvs/eland.mdl"

$bodygroup
Each bodygroup is one or more SMD files that act as meshes. If there is more than one, you will be able to switch between them in game.

The order of the bodygroup matters, as it determines their index in game. First bodygroup is 0, second is 1, etc.

// A bodygroup with a single mesh.
$bodygroup "base"
{
studio "base.smd"
}

// A bodygroup with multiple options. blank indicates an option for nothing at all.
$bodygroup "turret"
{
studio "turret_90.smd"
studio "turret_60.smd"
studio "turret_20.smd"
blank
}

$surfaceprop
Surface property of the model. Determines impact sounds and effects. Generally you want to use "metalvehicle" for vehicles.
See: https://developer.valvesoftware.com/wiki/Material_surface_properties
$surfaceprop "metalvehicle"

$cdmaterials
Defines a folder path to look for materials in. You can have multiple.
$cdmaterials "models\8z\lvs\eland\"
$cdmaterials "models\8z\lvs\weapons\"

$sequence
Sequences are animations. Animations are also animations, but sequences are cooler. You will need at least one idle sequence. They're also relevant for poseparamaters, which will be covered later.
$sequence "idle" {
"anims\idle.smd"
fadein 0.2
fadeout 0.2
fps 30
}

$collisionmodel
Assigns a SMD file as the collison mesh and gives it physical properties. There's a bunch of numbers you can tweak but 1 inertia and 0 damping is usually best. If your collision mesh has more than one convex shape, you must include $concave.
Vehicle mass is adjusted by LVS. You only need to worry about it for non-vehicle things like wrecks. 1000 is pretty standard.
$collisionmodel "physics.smd"
{
$mass 1000
$inertia 1
$damping 0
$rotdamping 0
$rootbone " "
$concave
}

Compile and View
After writing your QC file, you can compile it through Crowbar. You probably want to create a new folder under garrysmod/addons/ and point the output there.

Press the compile button, wait, and check the output box. Scroll through it and look for anything bad, like lines starting with ERROR. If successful, press "Use In View" at the bottom to open HLMV.


Half-Life Model Viewer is a tool to view compile Source models. Your model will have missing textures - this is normal.

For now, enable "Bones" and "Physics Model" to confirm they look as you would expect. If not, adjust as needed, recompile, and refresh the viewer with F5.

After the model looks good, you can boot up GMod to check it out in game. With Extended Spawnmenu, you can spawn it by finding your addon folder in Legacy Addons.

Once you have the body and wheel models working, it's time to turn it into a real vehicle.
Part 3: Lua Configuration
Now, it is time to bring the vehicle to life.

Create a folder in this format, replacing addon name and vehicle name with your own.
garrysmod/addons/YOUR_ADDON_NAME/lua/entities/lvs_wheeldrive_VEHICLENAME

Go to this page, download the three files cl_init.lua, init.lua, and shared.lua, and put them into your folder.
https://github.com/Blu-x92/lvs_cars/tree/main/lua/entities/lvs_wheeldrive_template

Open and configure each file. The template has a lot of documentation on what each line does, so I won't elaborate here.

Broadly speaking, shared.lua configures the vehicle's appearance, performance, and weapons, init.lua sets up the seats, wheels, engine, armor and weakspots, and cl_init.lua is for clientside logic. For certain things like turrets and tank optics, it is convention to create a new file - refer to the other vehicles' code in the repository, copy the respective files, and also include/AddCSLuaFile them.

Once you're done, reload the game and check if the vehicle exists in the spawnmenu. If it's not there, check for any errors in your console.

Hopefully, you'll now have a working vehicle to drive around with!

Properly loading Lua files
Tracks/turrets don't work? Weird clientside errors? You might not be loading everything correctly!

If your entity is a folder, Garry's Mod will load init.lua serverside only. In order for other lua files to load, you must use include() and AddCSLuaFile() on the other files.

By convention, GMod Lua files indicate the realm they're intended to be in through prefixes. cl_ is client, sh_ is shared, and sv_ is serverside. See below for an example of how to load these files properly.

cl_init.lua
-- for CL and SH files include("cl_optics.lua") include("sh_turret.lua")

init.lua
-- For CL files AddCSLuaFile( "cl_init.lua" ) -- For SH files include("shared.lua") include("sh_turret.lua") -- Also for SH files. This marks the Lua files to be sent to the client; this is only required serverside. AddCSLuaFile( "shared.lua" ) AddCSLuaFile( "sh_tracks.lua" ) -- For SV files include("sv_myfile.lua")

If your entity is a single Lua file, you only need to call AddCSLuaFile() to load the lone file. You really shouldn't do this for LVS vehicles, as they're modular and have a ton of code in each file.

A tip on hot-loading
While restarting the game will always update the changes you make, this takes a long time. Certain things do not need a game restart to be refreshed.

Creating a new legacy addon requires running the command reload_legacy_addons and a map change.

Updating existing Lua files will apply changes immediately. You may have to spawn a new vehicle to see the changes.

Creating new Lua files requires a map change for new files to load.

Updating a model can be done by running the console command r_flushlod. Be aware that this will crash your game under certain circumstances. Collision meshes are NOT updated by r_flushlod. A restart is necessary.

Updating VMT files will apply changes immediately most of the time.
Part 3.1: Armor
LVS has a damage region system for more detailed penetration modelling. Each damage region is a box that intercepts traces and does something, such as armor blocking the damage or ammo racks catching on fire.

Vehicles have a base armor defined by ENT.DSArmorIgnoreForce. This is the amount of force required to damage the vehicle if the trace did not hit anything else.

Define a piece of armor like so:
self:AddArmor( pos, ang, mins, maxs, health, num_force_ignore ) -- mins and maxs are Vectors defining the size of the armor -- num_force_ignore is the minimum bullet-damage-force that is required to deal damage. It gets added to ENT.DSArmorIgnoreForce which acts as a minimum Armor variable for the entire vehicle. So the actual armor value would be ENT.DSArmorIgnoreForce + num_force_ignore

Consult the base vehicles and the LVS documentation for more details.
Part 4: Pose Parameters
Once you have a basic functional vehicle working, we will be able to add the bells and whistles to it.

Working doors and hatches, rotating turrets, all that jazz is accomplished with the use of Pose Parameters. Pose Parameters are a set of poses corresponding to a value range. By configuring a model with pose parameters, LVS can assign a value to tell the game what pose it should look like.

For example, a pose parameter of a door has a range of 0 - 1. At 0, it's fully closed. At 1, it's fully open. We will need two poses for it, one where the door is closed and one where it's open, and the game will handle the in-between.

To create a pose parameter, we need several things:
  • A neutral animation based on the idle sequence;
  • A weightlist, to tell the model what bones should be moving;
  • Several one-frame animations to define the poses;
  • A $poseparameter key value to define the range;
  • And finally, a $sequence file where everything is put together.

In Blender
Let's start with the easiest pose parameter, a door or hatch. All we need is the door hinge bone, and 2 key frames for it. Hopefully you have the door rigged by now.

Create a new animation and name it something like "poses". This one animation will contain all the poses we need. Select the armature, go into Pose mode, and enable recording in the Timeline.

Make two key frames. The first should be identical to the neutral pose.
The second should also be identical except the door hinge, which is rotated to the open position.

Save and export this sequence. That's all we need!

In the QC file
First, create a "neutral" animation based on your idle sequence. This is necessary as our pose parameter sequence should only contain the difference in movement. You only need one of these for all pose parameters.
$animation neutral "anims/idle.smd" frames 0 0

Second, create the pose parameter and assign the range of values. For doors and hatches, the range is 0 to 1.
$poseparameter "door" 0 1 loop 0

Next, create a weightlist, and assign the hinge bone with a weight of 1.
$weightlist "door" { "door_hinge" 1 }

After that, create two animations for the two poses. Set the frame number to the corresponding one you see in Blender, and assign the weightlist.
$animation door1 "anims/poses" frame 0 0 subtract neutral 0 weightlist door $animation door2 "anims/poses" frame 1 1 subtract neutral 0 weightlist door

Finally, the sequence. Assign the two animations and the pose parameter name. The blend width should be the same number as the amount of animations. The other stuff needs to be there, but don't have to change.
$sequence "door" { "door1" "door2" blend "door" 0 1 blendwidth 2 fadein 0.2 fadeout 0.2 autoplay delta }

Compile the model and view in HLMV. In the sequence tab, you should see a slider with your pose parameter name on it. Drag it around and see if it moves as you intend.
Part 4.1: Doors
Doors and hatches are the simplest kind of pose parameters, as they only require 2 poses and a value of 0-1. After you create the pose parameter, you will need to create a door handler in LVS that acts as an activation box for the door.

In init.lua:
function ENT:OnSpawn( PObj ) local DriverSeat = self:AddDriverSeat( Vector(0,20,23), Angle(0,0,0) ) DriverSeat.HidePlayer = true local handler = self:AddDoorHandler( "door", Vector(0,35,50), Angle(0,0,-35), Vector(-15,-15,-10), Vector(15,15,10), Vector(-15,-15,-10), Vector(15,15,10)) handler:LinkToSeat( DriverSeat ) handler:SetSoundOpen( "lvs/vehicles/generic/car_hood_open.wav" ) handler:SetSoundClose( "lvs/vehicles/generic/car_hood_close.wav" ) end
When adding a door, you need to specify its location, rotation, and closed/open sizes.

By linking a door to a seat, using that door while open will put the player in that seat. Doors can exist without a linked seat - they just won't do much besides flopping open and close.

You may want to enable the developer convar to view the interaction box of the door.
Part 5: Turrets and Weapons
Now we can move on to a more complex pose parameter, turrets.

Most tank turrets can move horizontally (yaw) and vertically (pitch). This requires two pose parameters with a different range of values representing the degree of rotation. Yaw rotation is slightly complicated by the fact that it is a 360 degree rotation that loops around itself.

Additionally, the turret needs an attachment at its muzzle, so LVS knows where the rounds come out from. If you have a co-axial MG, it will need its own attachment as well.

In Blender
Create 2 new key frames for the turret pitch, and 5 more for the turret yaw.

For the pitch, set one pose for its lowest possible elevation and one for its highest. Write down how many degrees the turret is moving in each direction - this will be its pose parameter range.

For yaw, there will be 5 poses, each representing 90 degrees of rotation. The first and last pose will look identical, while the rest will rotate counterclockwise (left) 90, 180, and 270 degrees respectively.

Save the pose sequence with the new frames.

In the QC file
The setup will look pretty similar to the door, except the value range is different, and that the yaw will have "wrap" as a key - this tells the game the parameter wraps around on itself.

For this example, assume the keyframes of pitch and yaw poses are 6-7, and 8-12.

Note the negative value of pitch is the lowest it can aim, and the first pose is the lower pose.

$poseparameter "turret_pitch" -5 15 $weightlist turret_pitch { "barrel" 1.0 } $animation pitch1 "anims/poses" frame 6 6 subtract neutral 0 weightlist turret_pitch $animation pitch3 "anims/poses" frame 7 7 subtract neutral 0 weightlist turret_pitch $sequence turret_pitch { pitch1 pitch3 blendwidth 2 blend "turret_pitch" -5 15 } weightlist turret_pitch delta autoplay $poseparameter "turret_yaw" 0 360 loop 360 wrap $weightlist turret_yaw { "turret" 1.0 } $animation turret_yaw1 "anims/poses" frame 8 8 subtract neutral 0 weightlist turret_yaw $animation turret_yaw2 "anims/poses" frame 9 9 subtract neutral 0 weightlist turret_yaw $animation turret_yaw3 "anims/poses" frame 10 10 subtract neutral 0 weightlist turret_yaw $animation turret_yaw4 "anims/poses" frame 11 11 subtract neutral 0 weightlist turret_yaw $animation turret_yaw5 "anims/poses" frame 12 12 subtract neutral 0 weightlist turret_yaw $sequence turret_yaw { turret_yaw1 turret_yaw2 turret_yaw3 turret_yaw4 turret_yaw5 blendwidth 5 blend "turret_yaw" 0 360 } weightlist turret_yaw delta autoplay

Compile the model and check the poseparameter in HLMV.

For the attachment, you need to specify the bone, translation, and rotation. You probably don't know the correct value, so put in zeroes for now.
$attachment "muzzle_turret" "barrel" 0 0 0 rotate 0 0 0 $attachment "muzzle_mg" "barrel" 0 0 0 rotate 0 0 0

Compile and switch to the Attachments tab. Here, you can select your attachment and play with the translation and rotation and see the changes immediately. Remember that red (X) is forward, green (Y) is right, and blue (Z) is up.

After you have the right attachment values, copy the contents in "QC String" back into the QC file, replacing your previous one. Compile again and check that the position is correct.

In Lua
Create sh_turret.lua in the same folder and copy the contents from the Sherman tank here.
https://github.com/Blu-x92/lvs_cars/blob/main/lua/entities/lvs_wheeldrive_dodsherman/sh_turret.lua

Spawn your vehicle and see if the turret is pointing and moving where you expect. If the turret movement is inverted, adjust TurretPitchMul or TurretYawMul as needed.

Next up, weapons. Copy the ENT:InitWeapons() function from the Sherman into your shared.lua.
https://github.com/Blu-x92/lvs_cars/blob/main/lua/entities/lvs_wheeldrive_dodsherman/shared.lua

Each weapon is created by assigning a table containing some fields like name and icon, and functions that performs the attack. To start with, you will want to change the muzzle to the one your model is using. Look for the following function:
weapon.Attack = function( ent ) local ID = ent:LookupAttachment( "turret_machinegun" ) -- Replace this with your attachment local Muzzle = ent:GetAttachment( ID ) if not Muzzle then return end local bullet = {} bullet.Src = Muzzle.Pos bullet.Dir = Muzzle.Ang:Up() -- Replace with Forward() or Right() depending on the direction of your attachment -- Other stuff end

Once you've done that, you can play with the values as you want. The weapons system can do basically anything as long as you have the technical know-how, but you can also just copy from other vehicles and tweak the values a bit.
Closing Words
Hopefully this guide will prove helpful to you. If you have any corrections, suggestions or questions, leave a comment or join one of the Discord servers below.

If you would like a reference project, my workfiles for the Eland can be found here.
https://github.com/TheOnly8Z/lvs_eland

If you're interested in my work, consider joining my personal discord, Hyperrealism, where I cover development progress on addons like the Eland.[discord.gg]


Also consider joining the LVS Discord[discord.gg]. They've provided a lot of help for me and will certainly be eager to help you as well.
4 Comments
Prickle Pants May 28 @ 8:44am 
underrated af
Limule May 26 @ 10:10am 
Really nice
Po Dec 28, 2023 @ 12:21pm 
instructions unclear,i am now a 2019 Chevy silverado
Egor4k Dec 22, 2023 @ 5:02am 
Thank you so much!