Suty
Drug Sell System

Events & Handlers

Public event hooks third-party resources can listen to (XP, stats, gang loyalty, Discord logs…) without needing the source

🪝 Events & Handlers

suty-drugsell fires public events on key moments of the sale flow. Other resources can listen to these events to add their own behavior — XP systems, stat trackers, Discord webhooks, gang loyalty, achievements, leaderboards — without modifying or shipping the suty-drugsell source.

Two events fire on a successful sale: one on the server, one on the seller's client. Both deliver the same payload table.

ℹ️ Today, only successful sales fire an event. Refused sales, failed validations, and "sale started" do not have hooks yet. See What does NOT fire at the bottom.


Server event — suty-drugsell:sold

Fires once per successful sale. Listen from any server-side script.

AddEventHandler('suty-drugsell:sold', function(src, payload)
    -- src      : number    player server id (the seller)
    -- payload  : table
    --
    --   payload.drug         : string  -- item name sold (e.g. 'cocaine_bag')
    --   payload.quantity     : number  -- units sold this transaction
    --   payload.totalPrice   : number  -- total money received
    --   payload.pricePerUnit : number  -- random per-unit price used
    --   payload.pedNet       : number  -- network id of the customer NPC
end)

It's a plain TriggerEvent (not a NetEvent), so only other server-side resources can listen — players cannot spoof it from the client.


Client event — suty-drugsell:client:sold

Fires on the seller's client right after the sale completes. Useful for floating XP popups, sound effects, NUI notifications, screen flashes.

RegisterNetEvent('suty-drugsell:client:sold', function(payload)
    -- Same payload as the server event (no `src` — it's the local player).
    --   payload.drug         : string
    --   payload.quantity     : number
    --   payload.totalPrice   : number
    --   payload.pricePerUnit : number
end)

Examples

1. Award XP per sale (server)

AddEventHandler('suty-drugsell:sold', function(src, p)
    local xp = math.floor(p.totalPrice * 0.1)              -- 10% of revenue
    exports['my-xp']:AddXp(src, 'dealing', xp)
end)

2. Per-drug XP rates (server)

local XP = {
    weed_bag    = 4,
    cocaine_bag = 12,
    meth_bag    = 18,
}

AddEventHandler('suty-drugsell:sold', function(src, p)
    local perUnit = XP[p.drug] or 5
    exports['my-xp']:AddXp(src, 'dealing', perUnit * p.quantity)
end)

3. Track total dollars sold (server)

local total = {}   -- [citizenid] = number

AddEventHandler('suty-drugsell:sold', function(src, p)
    local Player = exports.qbx_core:GetPlayer(src)
    if not Player then return end
    local cid = Player.PlayerData.citizenid
    total[cid] = (total[cid] or 0) + p.totalPrice
end)

4. Discord webhook log (server)

AddEventHandler('suty-drugsell:sold', function(src, p)
    local name = GetPlayerName(src) or 'unknown'
    PerformHttpRequest('https://discord.com/api/webhooks/...', function() end, 'POST',
        json.encode({
            embeds = {{
                title = 'Drug Sale',
                color = 15844367,
                fields = {
                    { name = 'Player', value = ('%s (src %d)'):format(name, src), inline = false },
                    { name = 'Drug',   value = p.drug,                            inline = true  },
                    { name = 'Amount', value = ('%dx'):format(p.quantity),        inline = true  },
                    { name = 'Income', value = ('$%d'):format(p.totalPrice),      inline = true  },
                },
            }},
        }),
        { ['Content-Type'] = 'application/json' })
end)

5. Floating XP popup on the client

RegisterNetEvent('suty-drugsell:client:sold', function(p)
    local xp = math.floor(p.totalPrice * 0.1)
    lib.notify({
        title = 'Dealing XP',
        description = ('+%d XP for selling %dx %s'):format(xp, p.quantity, p.drug),
        type = 'success',
    })
end)

6. Gang loyalty (server)

AddEventHandler('suty-drugsell:sold', function(src, p)
    local Player = exports.qbx_core:GetPlayer(src)
    if not Player then return end
    local gang = Player.PlayerData.gang and Player.PlayerData.gang.name
    if not gang or gang == 'none' then return end
    exports['my-gangs']:AddLoyalty(gang, math.ceil(p.totalPrice / 50))
end)

Listening from a brand-new resource

A minimal add-on that listens to drug sales is just two files:

fxmanifest.lua

fx_version 'cerulean'
game 'gta5'

server_script 'server.lua'
client_script 'client.lua'   -- only if you need the client hook

server.lua

AddEventHandler('suty-drugsell:sold', function(src, p)
    -- your logic here
end)

client.lua

RegisterNetEvent('suty-drugsell:client:sold', function(p)
    -- your UI here
end)

That's it. No dependency on suty-drugsell, no access to its source required, and the add-on keeps working across updates as long as payload field names stay the same (they will — new fields may be added but existing ones won't be renamed or removed).


What does NOT fire

For transparency, these flows have no public hook today. Open a ticket on Discord if you need one and we'll add it:

  • A sale that's refused (NPC undercover cop / police alert chance)
  • A sale that fails validation (no drugs in inventory, not in zone, etc.)
  • A player starting a sale conversation (before the deal lands)
  • Police alerts triggered by a sale

Compatibility note

The events have been stable since version 1.1.0. If you're on an older build, update before relying on them.