Back to Projects

Source Code

lua
-- weapon_grapple_pistol.lua
-- Simple grappling pistol for DarkRP (or sandbox). No workshop content needed.

AddCSLuaFile()

SWEP.Base           = "weapon_base"
SWEP.PrintName      = "Grapple Pistol"
SWEP.Author         = "YourServer"
SWEP.Instructions   = "Hold Right Click: grapple. Reload: cancel."
SWEP.Spawnable      = true
SWEP.AdminOnly      = false
SWEP.Category       = "Tools"

SWEP.UseHands       = true
SWEP.ViewModel      = "models/weapons/c_pistol.mdl"
SWEP.WorldModel     = "models/weapons/w_pistol.mdl"
SWEP.ViewModelFOV   = 62

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"

-- Config
local MAX_DIST    = 4000
local MIN_DIST    = 120
local STOP_DIST   = 80
local PULL_FORCE  = 450
local UP_BOOST    = 80
local COOLDOWN    = 0.1

function SWEP:Initialize()
    self:SetHoldType("pistol")
    if SERVER then
        self:SetGrappling(false)
        self:SetGrapplePos(Vector(0, 0, 0))
    end
end

function SWEP:SetupDataTables()
    self:NetworkVar("Bool", 0, "Grappling")
    self:NetworkVar("Vector", 0, "GrapplePos")
end

function SWEP:CanPrimaryAttack()
    return true
end

function SWEP:PrimaryAttack()
    -- no action on primary; grapple uses right click
end

function SWEP:SecondaryAttack()
    if not IsValid(self.Owner) then return end
    if self:GetGrappling() then return end
    self:SetNextSecondaryFire(CurTime() + COOLDOWN)

    local shootPos = self.Owner:GetShootPos()
    local dir = self.Owner:GetAimVector()

    local tr = util.TraceLine({
        start  = shootPos,
        endpos = shootPos + dir * MAX_DIST,
        filter = self.Owner
    })

    if not tr.Hit or tr.HitSky then
        self.Owner:EmitSound("buttons/button10.wav")
        return
    end

    local dist = tr.HitPos:Distance(self.Owner:GetPos())
    if dist < MIN_DIST then
        self.Owner:EmitSound("buttons/button10.wav")
        return
    end

    if SERVER then
        self:SetGrapplePos(tr.HitPos)
        self:SetGrappling(true)
    end
    self.Owner:EmitSound("weapons/airboat/airboat_gun_energy1.wav", 75, 130, 0.7, CHAN_AUTO)
end

function SWEP:Reload()
    self:CancelGrapple()
end

function SWEP:CancelGrapple()
    if SERVER and self:GetGrappling() then
        self:SetGrappling(false)
        self:SetGrapplePos(Vector(0, 0, 0))
        if IsValid(self.Owner) then
            self.Owner:EmitSound("buttons/button18.wav", 60, 100, 0.6)
        end
    end
end

function SWEP:Think()
    if CLIENT then return end
    if not IsValid(self.Owner) then return end

    if not self.Owner:KeyDown(IN_ATTACK2) then
        -- stop when right click is released
        if self:GetGrappling() then self:CancelGrapple() end
        return
    end
    if not self:GetGrappling() then
        -- trigger trace once on hold
        self:SecondaryAttack()
        return
    end

    local ply = self.Owner
    local pos = ply:GetPos()
    local target = self:GetGrapplePos()
    if not target then
        self:CancelGrapple()
        return
    end

    local delta = target - pos
    local dist = delta:Length()

    if dist < STOP_DIST then
        self:CancelGrapple()
        return
    end

    local dir = delta:GetNormalized()
    local vel = dir * PULL_FORCE + Vector(0, 0, UP_BOOST)
    -- Reduce existing velocity influence so it feels snappier
    ply:SetVelocity(vel - ply:GetVelocity() * 0.3)
end

-- Optional beam effect (clientside)
if CLIENT then
    function SWEP:DrawHUD()
        -- simple crosshair line
        surface.SetDrawColor(0, 200, 255, 180)
        local x, y = ScrW() / 2, ScrH() / 2
        surface.DrawLine(x - 8, y, x + 8, y)
        surface.DrawLine(x, y - 8, x, y + 8)
    end

    function SWEP:PostDrawViewModel(vm, ply, weapon)
        if not weapon.GetGrappling or not weapon.GetGrapplePos then return end
        if weapon:GetGrappling() then
            render.SetMaterial(Material("cable/cable2"))
            local attach = vm:GetAttachment(1)
            local startPos = attach and attach.Pos or vm:GetPos()
            render.DrawBeam(startPos, weapon:GetGrapplePos(), 3, 0, 1, Color(160, 160, 160, 200))
        end
    end

    function SWEP:DrawWorldModel()
        self:DrawModel()
        if not self.GetGrappling or not self.GetGrapplePos then return end
        if not self:GetGrappling() then return end

        local owner = self:GetOwner()
        local startPos
        if IsValid(owner) then
            local attach = owner:GetAttachment(owner:LookupAttachment("anim_attachment_RH"))
            startPos = attach and attach.Pos or owner:GetShootPos()
        else
            startPos = self:GetPos()
        end

        render.SetMaterial(Material("cable/cable2"))
        render.DrawBeam(startPos, self:GetGrapplePos(), 3, 0, 1, Color(160, 160, 160, 200))
    end
end

HG Grapple Gun

Game
LuaGarry's ModSource Engine

About This Project

A Garry's Mod SWEP (scripted weapon) that gives players a grapple pistol. Latch onto surfaces and pull yourself across the map. Handles configurable pull force, max grapple distance, and cooldown, and follows the standard GMod shared/init/cl_init Lua structure.

Key Features

  • Latch-and-pull grapple physics
  • Configurable pull force and distance
  • Cooldown and ammo handling
  • Standard GMod SWEP structure