Vehicle Tutorial Part 3

From Mod Wiki

More Advanced Joints

More advanced vehicle rig showing the joint positions and names.

You can now add extra joints as needed for more passengers, gunners, suspension, effects locations and exit points.

  • Passengers - For each passenger you want to add, you will need a joint for the player location, and a joint for their first-person camera view.
  • Gunners - This can be set up like a passenger, but with more scripting needed, as described in the Scripting section below.
  • Suspension - These joints should be parented to the origin joint, and have the related wheel joint as a child.
  • Exit points - These joints define more places that players may appear when they exit the vehicle.
  • Effects locations - Used as an attachment location for effects such as smoke and fire when the vehicle is damaged.

Related Files

More Scripting

In Part 2 you learnt how to get your vehicle into the game in a very rough state. Here we are going to expand on what we developed last time to improve it


Now that we have suspension joints we can hook them up so that we can see the wheels moving up and down, following the ground. To the text part of the wheel template add the following line:

    "suspension"                    "vehicle_buggy_tute_FrontBackParm_LeftRightParm_suspension"

This makes the wheel look for a stringMap to describe the suspension. The simplest type is vertical, which is the one we are going to use here:

stringMap vehicle_buggy_tute_front_right_suspension {
    "type"                          "vertical"
    "joint"                         "front_right_suspension"
stringMap vehicle_buggy_tute_front_left_suspension {
    "type"                          "vertical"
    "joint"                         "front_left_suspension"
stringMap vehicle_buggy_tute_rear_right_suspension {
    "type"                          "vertical"
    "joint"                         "rear_right_suspension"
stringMap vehicle_buggy_tute_rear_left_suspension {
    "type"                          "vertical"
    "joint"                         "rear_left_suspension"

These can go in the vscript file, outside the vehicleDef

Shooting it

At the moment your vehicle will not take damage when people shoot it - this is because it does not fit in any of the target lists used by the bullet damage definitions. To put it into these target lists we need to add the vehicle to some "collections". You'll need to add it to the following collections by adding these keys to the entityDef:

    "collection_antivehicle"       "antivehicle"
    "collection_vehicles_light"    "vehicles_light"
    "collection_vehicles_gdf"      "vehicles_gdf"

The first of these allows it to be targeted by anti-vehicle turrets. The second marks it as being a light vehicle - this means it will take more damage from bullets than if it were markes as a heavy vehicle. The last one allows it to be damaged by various map entities, for example Strogg energy walls.

Damage Effects

You'll probably now notice that your vehicle will have flames/smoke coming from the origin of the model when it gets significantly damaged. This is where your damage effect joint comes in - again, these keys go in the entityDef:

    "joint_damage_smoke"           "effects"
    "joint_damage_fire"            "effects"

This will make the damage effects play at the joint named effects. You can adjust the effects that play, and when they play, with the following keys:

   "damage_smoke"                  "70"
   "damage_level1"                 "50"
   "damage_level2"                 "30"
   "damage_level3"                 "10"
   "fx_damage_level_smoke"         "effects/vehicles/generic_smoke"
   "fx_damage_level1"              "effects/vehicles/vehicle_flames_small"
   "fx_damage_level2"              "effects/vehicles/vehicle_flames_medium"
   "fx_damage_level3"              "effects/vehicles/vehicle_flames_large"

The smoke effect will play at the same time as the damage level 1, 2, or 3 effects. When the vehicle's health drops below the value specified by "damage_smoke" etc the corresponding effect will be played. I have given the default values here.

Collision Damage

Your vehicle will not currently do any damage to anything else in a collision. For this you'll need to make a new damage type for the collision:

damageDef damage_buggy_tute_collide {
    damage                          "damage_buggy_tute_collide"

    team_kill_cvar                  "g_allowComplaint_vehicles"

    tt_obituary                     "tooltips/killmsgs/vehicles/buggy_tute"
    tt_obituary_unknown             "tooltips/killmsgs/vehicles/buggy_tute/empty"
    tt_obituary_team_kill           "tooltips/killmsgs/vehicles/buggy_tute/teamkill"
    tt_obituary_self                "tooltips/killmsgs/vehicles/driving"

damageFilter damage_buggy_tute_collide {
    type {
        target  "target_player_all"
        damage  100
    type {
        target  "target_veh_all"
        damage  100
    type {
        target  "target_supply_crate"
        damage  200
    type {
        target  "target_deployables_all"
        damage  100

I put these in the buggy_tute.def file, below the vehicle's entityDef. Then you'll need to add the following key to the vehicle's entityDef:

   "dmg_collide"                    "damage_buggy_tute_collide"

This sets up a damage filter listing the target sets that are damaged by the vehicle, and values that scale the damage caused - the damage caused varies with the speed of the collision. The damage definition also specifies a cvar that can be used to enable and disable complaints for team kills by this damage type, and obituary names. I'll describe how to set the obituaries up below.


Anything to be displayed on the HUD needs to be a localized string. Its pretty easy to create new strings, but there are a few files involved. First of all you'll want to create a few directories under your mod directory: localization, localization/locstr, localization/english, and localization/english/strings. Now create a couple of files to put your new strings in: localization/locstr/buggy_tute.locstr, and localization/english/strings/buggy_tute.lang. Into the locstr files go locString definitions, which are named strings that can have arguments. Into lang files go tables of string values. This allows there to be lang files for each language, having different strings for each language but with them all looking the same from the rest of the game's code & data.

Into the locstr file add:

locString game/vec/buggy_tute {
    text "#str_10000100"

The number in the string is used to identify the number of the localized string in the lang file. All strings in retail ETQW are numbered less than 10000000, so values over that are safe. I like to set aside a range of values for a category of strings - in this case I'm setting aside the range 10000100 -> 10000200 for this vehicle. This helps prevent the situation where you have to search through all the files to find an unused number.

  • Note: You do not have to use this exact numbering scheme, for example to prevent conflicts you could call your string "#str_buggy_001" etc. This is slightly easier to remember and less potential string conflicts to worry about.

In the lang file all the strings must be between a pair of braces, so add:

    "#str_10000100" "Buggy!"

Any further strings I add for this car I'll be adding in between the two braces. You can now hook this up to the vehicle entity by changing the value of its "info_name" key to "game/vec/buggy_tute". When you look at the vehicle in the game you should now see "Buggy!" :)


There are three obituaries we need to fill in (we referenced them above when we made a collision damage definition). The strings for these are referenced via a tooltip definition, so we need to make those:

tooltip tooltips/killmsgs/vehicles/buggy_tute {
    text "game/obit/buggy_tute"

tooltip tooltips/killmsgs/vehicles/buggy_tute/empty {
    text "game/obit/buggy_tute_empty"

tooltip tooltips/killmsgs/vehicles/buggy_tute/teamkill {
    text "game/obit/buggy_tute_team"

I added these at the end of the buggy_tute.def file. Next we need to set up the localized string definitions for these:

locString game/obit/buggy_tute {
    text "#str_10000101"

locString game/obit/buggy_tute_empty {
    text "#str_10000102"

locString game/obit/buggy_tute_team {
    text "#str_10000103"

And finally the actual localized strings:

    "#str_10000101" "%b^0 &lbrBuggy&rbr %a"
    "#str_10000102" "&lbrBuggy&rbr %a"
    "#str_10000103" "%b^1 &lbrBuggy&rbr ^0%a"

These strings have some interesting escape sequences in them. %a and %b are replaced with the names of the name of the killer and the killed respectively, &lbr and &rbr are replaced by left square brackets and right square brackets respectively, and the caret ( ^ ) symbol allows the text colour to be changed.

Command Map

At the moment your vehicle won't be visible on the command map. This is easy fixed:

   "icon_size_cm"                  "16"
   "mtr_commandmap"                "guis/assets/commandmap/icon_vehicle"
   "mtr_commandmap_unknown"        "guis/assets/commandmap/icon_vehicle"

The first key sets the size of the icon and the other two set the material to be used for the icon. The unkown material isn't really important in this case, you should just set it to the same as the normal material as I have done here.

Control Binding Context

In ETQW bindings can be context sensitive, and by using a series of cvars (one for each vehicle type) you can customize the control layout for each vehicle. To set the name of the context cvar your vehicle uses you can add the following key to your vehicle's entityDef:

    "control_context"              "g_bind_context_buggy"

Note that you can call the cvar whatever you like. To create the cvar you will need to pull the console down and set it by typing something like set g_bind_context_buggy vehicle. This can be included in config files with your mod to ensure people have the cvars created.

Tuning the Steering

The default values for the steering tend to be pretty good for most cases. The steering works on the principle that there is a direction in which the player wants to go - by pressing left and right they alter the direction they want to go in. The game then tries to steer towards that direction. When driving a real car you automatically adjust the steering wheel as you go over bumps etc by feel - you do not get any of this feeling from a keyboard, normally by the time you see your car veering to one side from hitting a bump its already too late to correct it neatly. Keyboards are also an on/off input device, so making subtle corrections is very difficult. The game is essentially trying to compensate for your lack of this feeling of the road surface and make all the subtle adjustments you aren't able to make.

There are a number of keys that can be set on the vehicle's entityDef to adjust the behaviour of the steering:

Key Default Value Description
steering_angle n/a The maximum steering angle.
simplesteer_forward_speed 2 The speed at which the desired direction of movement will change due to player input while moving forward.
simplesteer_reverse_speed -4 The speed at which the desired direction of movement will change due to player input while moving in reverse.
simplesteer_centering_speed_min 2 The minimum speed at which the steering will tend to center back towards the current movement direction.
simplesteer_centering_speed_max 15 The maximum speed at which the steering will tend to center back towards the current movement direction.
simplesteer_centering_ramp_threshold 20 The vehicle speed at which the centering speed will drop to the minimum value (as the vehicle speeds up the steering becomes less twitchy).
simplesteer_centering_speed_air 0.5 The speed at which the steering will tend to center back towards the current movement direction while the vehicle is airborne (so it doesn't lose the direction of movement too fast when jumping).
simplesteer_reverse_angle_scale -0.5 Scales the amount/direction of steering when moving in reverse.

For example if you wish the player's input to result in the vehicle turning faster you can increase simplesteer_forward_speed.

Engine Sounds

One thing thats our vehicle lacks so far is sound. ETQW wheeled vehicles use three looping samples that are cross-faded and pitched with varying speeds and accelerator input values. In addition to this there is an engine start sound and an engine stop sound for when the driver gets in or out. All of these things are defined in the entityDef.

Firstly you'll need to set the sound control method:

    "sound_control"                 "wheeled"

You need to specify the samples to be used. We use an idle sample & a drive sample, where idle is cross-faded with the drive as speed increases, and a "hard acceleration" sample which is faded in and out with speed and input. This is meant to emulate the ferocious exhaust note you hear on a vehicle that is accelerating hard.

    "snd_engine_start"              "sounds/vehicles/badger/engine/start"
    "snd_engine_stop"               "sounds/vehicles/badger/engine/stop"
    "snd_engine_idle"               "sounds/vehicles/badger/engine/idle"
    "snd_engine_drive"              "sounds/vehicles/badger/engine/drive"
    "snd_engine_hardaccel"          "sounds/vehicles/husky/engine/hardaccel"

In my case I'm reusing most of the sounds of the badger, but mixing in the husky acceleration sound to hopefully make it sound a bit more raw.

The first few parameters control the change in pitch of the samples as the vehicle changes pitch:

    "engine_pitch_low"              "1.5"
    "engine_pitch_high"             "2"
    "engine_speed_low"              "10"
    "engine_speed_high"             "150"

This sets the high & low levels of the pitch and the speeds at which they occur. At engine_speed_low the pitch will be engine_pitch_low and similarly for high speed. The pitch is linearly ramped between those two. The original pitch in the sample is 1, so 1.5 is 50% faster, and 0.5% is 50% slower.

The next few parameters control engine "spooling" - this is meant to emulate the fact that an engine does not speed up or slow down instantly:

    "engine_accel_spool_time"       "0.033"
    "engine_decel_spool_time"       "0.033"

This is how long it takes for the "engine speed" to reach the actual speed of movement, in seconds. 0.033 is instant (1 / 30 - ie, one 30fps game frame).

Next are the volume settings for the idle and drive samples:

    "engine_idle_min_speed"         "5.0"
    "engine_idle_max_speed"         "50.0"
    "engine_idle_min_vol"           "0.0"
    "engine_idle_max_vol"           "-50.0"
    "engine_idle_power"             "1.0"
    "engine_idle_fade_time"         "0.066"
    "engine_drive_min_speed"        "20.0"
    "engine_drive_max_speed"        "80.0"
    "engine_drive_min_vol"          "-20.0"
    "engine_drive_max_vol"          "-5.0"
    "engine_drive_power"            "0.3"
    "engine_drive_fade_time"        "0.066"

At the min speed the sample will be at min volume - at max speed the sample will be at max volume. The sample will change between the two depending on the power value. This adjusts the linearity of the curve - 1 is linear, greater than 1 is exponential (slow change at first, fast change later), and less than 1 (but greater than zero) changes fast at first and slowly later. The fade time governs how long it takes the sound to fade away when the engine is shut off. All volume settings are measured in decibels, with zero being the original sample volume.

Next are the acceleration sound tuning parameters - these are much more complicated. First of all there are parameters to allow the pitch to be adjusted semi-independently of the idle and drive samples:

    "engine_accel_pitch_mult"       "1.0"
    "engine_accel_pitch_offset"     "0.5"

The pitch used for the other samples is subtracted from the minimum pitch, multiplied by the pitch multiplier and added to the offset. This effectively allows the pitch to change over a different range of values, given the same speed range, with the offset value giving the minimum. The pitch and the volume can also be adjusted based on the yaw speed of the vehicle (see the Titan/MCP for examples).

The volume of the acceleration sound has three key points - a min, mid and a max value. Each has a speed and a corresponding volume, and the transition between min & mid and mid & max each have their own power values to adjust the linearity of the transition. It is also increased when going up steep slopes and when the player is accelerating, to emulate the engine straining.

    "engine_accel_min_speed"        "1.0"
    "engine_accel_mid_speed"        "20.0"
    "engine_accel_max_speed"        "80.0"
    "engine_accel_min_vol"          "-5.0"
    "engine_accel_mid_vol"          "5.0"
    "engine_accel_max_vol"          "-3.0"
    "engine_accel_power_low"        "0.1"
    "engine_accel_power_high"       "5.0"
    "engine_accel_fade_time"        "0.0"

Overdrive Sounds

There are default overdrive sound settings, but you may want to tweak those. They support pitch shifting much like the engine sounds:

    "snd_overdrive"                 "sounds/vehicles/husky/overdrive"
    "snd_overdrive_stop"            "sounds/vehicles/husky/overdrive/stop"

    "overdrive_pitch_low"           "0.9"
    "overdrive_pitch_high"          "1.5"
    "overdrive_speed_low"           "20"
    "overdrive_speed_high"          "90"

Miscellaneous Sounds

There are a couple of other sounds used by vehicles - the horn, and the low health warning. These are dead easy to configure:

    "snd_horn_loop"                 "sounds/vehicles/husky/horn"
    "snd_horn_stop"                 "sounds/vehicles/husky/horn/stop"

    "snd_health_warn"               "sounds/vehicles/misc/warning/ground/gdf"

The keys used should be pretty self-explanatory for these!

Click here to continue on to Part 4 of the Vehicle Tutorial.