Skip to content

Vehicle Turret

StyledStrike edited this page Jan 8, 2025 · 2 revisions

This page describes vehicle turrets. These are player-usable, predicted, lag-compensated weapons that only fire bullets, and is primarily meant to be used by passengers.

For vehicle weapons that can be switched and controlled by the driver, see Vehicle Weapons.

For tank cannons controlled by the driver, see TODO: Link to Tanks page.

Creating a turret

To create a turret, you have to use SERVERGlide.CreateTurret inside of your vehicle's SERVERENT:CreateFeatures, like this:

-- Turrets are set up on the server side
if SERVER then
    function ENT:CreateFeatures()
        local turret = Glide.CreateTurret( self, Vector( 0, 0, 50 ), Angle() )
    end
end
  • The first parameter is the Glide vehicle to attach a turret.
  • The second parameter is the turret's position, relative to the vehicle.
  • The third parameter is the turret's base angles, relative to the vehicle.

The turret's base stays static relative to the vehicle, while the turret's body can rotate. By default, the turret's base has a "axis" model, and the turret's body has a "soda can" model.

Turret default models

However, you can change them:

-- You can change the model of the turret's base itself
turret:SetModel( "..." )

-- And change the model of the rotating part of the turret, referred to as the "turret's body"
turret:SetBodyModel( "..." )

Turret parameters

After creating a turret, you might want to change how it behaves or sounds like.

Bullet offset

To change where bullets, muzzle flash and tracers comes from, you should use turret:SetBulletOffset. The offset is relative to the turret's body, in other words, it rotates with the turret.

Turret offset example Turret offset example

Fire rate and damage

-- You can set how often bullets are fired with this
turret:SetFireDelay( 0.5 ) -- Fire every half of a second

-- You can set how much damage each bullet deals with this
turret.BulletDamage = 10

-- You can make the bullets explosive by making this value larger than 0
turret.BulletExplosionRadius = 200

Sounds

-- You can set the looping sound that plays while the turret is being fired...
turret:SetShootLoopSound( "glide/weapons/b11_turret_loop.wav" )

-- and the sound that plays after the turret stops firing.
turret:SetShootStopSound( "glide/weapons/b11_turret_loop_end.wav" )

-- If you don't want loop/stop sounds, you can leave them empty.
turret:SetShootLoopSound( "" )
turret:SetShootStopSound( "" )

-- You can set a sound to play for every bullet fired like this.
-- It accepts either a path to a file...
turret:SetSingleShotSound( "weapons/airboat/airboat_gun_energy1.wav" )

-- or a Glide sound set.
turret:SetSingleShotSound( "Glide.InsurgentShoot" )

-- It also can be empty if you don't want to play it.
turret:SetSingleShotSound( "" )

Angle limits

-- To set a limit for the pitch angles, you can use these.
turret:SetMinPitch( -40 ) -- Don't let the turret aim higher than -40 degrees (Yes, it's a negative number)
turret:SetMinPitch( 50 ) -- Don't let the turret aim lower than 50 degrees (Yes, it's a positive number)

-- You can also limit the yaw angles. These are relative to the turret angle.
-- This example makes the turret body stay at the left side relative to the turret base.
turret:SetMinYaw( 0 )
turret:SetMaxYaw( 180 )

Controlling the turret

To make the turret usable, you'll need to tell it who is trying to use it. You can achieve that by doing this:

if SERVER then
    function ENT:CreateFeatures()
        -- Create the turret, setup it's parameters, etc...
        local turret = Glide.CreateTurret( self, Vector( 0, 0, 50 ), Angle() )

        -- Store the turret entity so we can tell who is going to use it later
        self.myTurret = turret
    end

    function ENT:OnUpdateFeatures()
        -- Make sure the turret entity still exists
        if IsValid( self.myTurret ) then
            -- Get the passenger from the second seat
            local user = self:GetSeatDriver( 2 )

            -- Or you could just use the vehicle driver directly
            -- local user = self:GetDriver()

            -- Let this player control the turret
            self.myTurret:UpdateUser( user )
        end
    end
end

Setting up a crosshair

-- Crosshairs are set up on the client side
if CLIENT then
    -- We're going to override `OnLocalPlayerEnter`, but
    -- we still want the base vehicle to handle it too.

    -- So, we are going to store our base vehicle class to use
    -- it's original `OnLocalPlayerEnter` function later.

    -- If your vehicle is a plane, helicopter, motorcycle, etc.
    -- replace "base_glide_car" with the correct class.
    DEFINE_BASECLASS( "base_glide_car" )

    -- Override this base class function
    function ENT:OnLocalPlayerEnter( seatIndex )
        self:DisableCrosshair() -- Make sure there's no crosshair for now

        -- If the local player entered the second seat...
        if seatIndex == 2 then
            -- Enable the crosshair for them.
            self:EnableCrosshair( { iconType = "dot", color = Color( 0, 255, 0 ) } )
        else
            -- For other seats, let the base class handle this.
            BaseClass.OnLocalPlayerEnter( self, seatIndex )
        end
    end

    -- Make the crosshair stay right at where the local player's Glide camera is aiming at
    function ENT:UpdateCrosshairPosition()
        self.crosshair.origin = Glide.GetCameraAimPos()
    end

    -- Disable the crosshair when the local player leaves the vehicle
    function ENT:OnLocalPlayerExit()
        self:DisableCrosshair()
    end
end

If you want to know more about crosshairs, check this page.

Manipulating the vehicle's model

Sometimes, the visual representation of your turret is part of the vehicle model itself.

On the Insurgent for example, the turret's "base" and machine gun are separated parts of the model with their own bones.

Insurgent's Turret

Placing the turret in the correct position

We are going to use the visual representation of the turret, but the actual turret entity is still where bullets are shot from, and where the muzzle flash and tracers are created.

Incorrect turret offset

Remember to tweak the offset when creating a turret with SERVERGlide.CreateTurret, and to tweak where the bullet comes from with turret:SetBulletOffset, so it is close enough to what your model looks like.

Correct turret offset

Hiding the turret entity

Instead of using turret:SetModel and turret:SetBodyModel, we should hide the turret using:

Glide.HideEntity( turret, true )
Glide.HideEntity( turret:GetGunBody(), true )

Rotating the vehicle's turret bones

Now here comes the tricky part. The turret is SERVER server side, but we want to manipulate the vehicle's bones on the CLIENT client side.

This will require storing the turret as a network variable. Also, because the turret's movements can be predicted by the player using it, we need to use the predicted angles to manipulate bones when necessary.

-- Define which seat index controls the turret.
local MY_TURRET_SEAT_INDEX = 1

-- We're going to override a few functions from the base vehicle,
-- but we might want the base vehicle to handle them too.

-- So, we are going to store our base vehicle class
-- to use it's original functions when necessary.

-- If your vehicle is a plane, helicopter, motorcycle, etc.
-- replace "base_glide_car" with the correct class.
DEFINE_BASECLASS( "base_glide_car" )

function ENT:SetupDataTables()
    -- Let the base class setup it's own network variables.
    -- Keep this, this is important!
    BaseClass.SetupDataTables( self )

    -- Let's store our turret as a network variable too.
    -- This will give us self:SetTurret and self:GetTurret.
    self:NetworkVar( "Entity", "Turret" )
end

-- This logic should be running on the server only
if SERVER then
    function ENT:CreateFeatures()
        -- Create the turret, setup it's parameters, etc...
        local turret = Glide.CreateTurret( self, Vector( 0, 0, 50 ), Angle() )

        -- Store the turret entity as a network variable, so we can
        -- get it's angles client side, and tell it who is going to use it server side.
        self:SetTurret( turret )
    end

    function ENT:OnUpdateFeatures()
        -- Make sure the turret entity still exists
        local turret = self:GetTurret()

        if IsValid( turret ) then
            -- Get the passenger from the "turret seat"
            local user = self:GetSeatDriver( MY_TURRET_SEAT_INDEX )

            -- Let this player control the turret
            turret:UpdateUser( user )
        end
    end
end

-- This logic should be running for every player (client side)
if CLIENT then
    -- Override this base class function
    function ENT:OnLocalPlayerEnter( seatIndex )
        self:DisableCrosshair() -- Make sure there's no crosshair for now

        -- If the local player entered the "turret seat"...
        if seatIndex == MY_TURRET_SEAT_INDEX then
            -- Enable the crosshair for them.
            self:EnableCrosshair( { iconType = "dot", color = Color( 0, 255, 0 ) } )

            -- Remember that the local player is using this turret,
            -- so we can use the predicted angles.
            self.isLocalPlayerUsingTurret = true
        else
            self.isLocalPlayerUsingTurret = false

            -- For other seats, let the base class handle this.
            BaseClass.OnLocalPlayerEnter( self, seatIndex )
        end
    end

    -- Make the crosshair stay right at where the player's Glide camera is aiming at
    function ENT:UpdateCrosshairPosition()
        self.crosshair.origin = Glide.GetCameraAimPos()
    end

    function ENT:OnLocalPlayerExit()
        -- Disable the crosshair when the local player leaves the vehicle
        self:DisableCrosshair()

        -- The local player is no longer predicting the turret angles
        self.isLocalPlayerUsingTurret = false
    end

    -- Override this base class function
    function ENT:OnActivateMisc()
        BaseClass.OnActivateMisc( self )

        -- Store the ID of the bone we want to manipulate
        self.turretBoneID = self:LookupBone( "your_turret_bone_name" )
    end

    -- Override this base class function
    function ENT:OnUpdateAnimations()
        BaseClass.OnUpdateAnimations( self )

        -- Make sure the turret is valid for the local player
        local turret = self:GetTurret()
        if not IsValid( turret ) then return end

        -- Get either:
        -- The predicted turret body angle, if the local player is using it;
        -- The networked turret body angle, if the local player is NOT using it.
        local bodyAng = self.isLocalPlayerUsingTurret and turret.predictedBodyAngle or turret:GetLastBodyAngle()

        -- Manipulate the bone(s) of your vehicle as needed
        self:ManipulateBoneAngles( self.turretBoneID, Angle( 0, bodyAng[2], 0 ) )
    end
end