Skip to content

API

SL-Interact is used from your own scripts through client exports. Every interaction belongs to the resource that added it and is removed automatically when that resource stops.

The examples below are taken from the example resource that comes with SL-Interact (sl_interact_demo, next to sl_interact in the download). Its code is open: put it in your resources folder, start it and type /slidemo to spawn every example in front of you.

local SI = exports.sl_interact
local id = SI:addCoords(vec3(147.6, -1035.8, 29.3), {
label = 'ATM',
icon = 'cash',
options = {
{ key = 'E', label = 'Use', onSelect = function(ctx) OpenAtm() end },
{ key = 'G', label = 'Check balance', serverEvent = 'bank:server:balance' },
},
})
-- Later: change it or remove it
SI:update(id, { status = { ok = false, label = 'Out of service' } })
SI:remove(id)

Every function returns an id (a number) to use with update and remove, or nil if the interaction was invalid. The reason is printed in the F8 console with the name of your resource.

Export Where the interaction goes
addCoords(coords, def) A fixed point in the world (vector3)
addLocalEntity(entity, def) An object, ped or vehicle created by this client (entity handle)
addEntity(netId, def) A networked entity. It shows whenever the entity exists nearby, even if it is created later
addModel(models, def) Every object, ped or vehicle of one or more models (name or hash)
addGlobalVehicle(def) Every vehicle
addGlobalPed(def) Every ped (NPC)
addGlobalObject(def) Every object
addGlobalPlayer(def) Every other player
addMany(list) Many at once (see many at once)

Without bones, an interaction on an entity covers the whole entity: the player can aim at any part of it, and the distance is measured to its body. With bones, it is that spot only. Both can live on the same vehicle: the trunk when aiming at the back, the vehicle menu anywhere else.

-- Every ped with this model
SI:addModel('a_m_y_business_01', { label = 'Businessman', icon = 'talk', reveal = 'look', options = { ... } })
-- The trunk of every vehicle, only when aiming at the back
SI:addGlobalVehicle({
label = 'Trunk', icon = 'box', bones = 'boot',
options = { { key = 'E', label = 'Open / close', onSelect = function(ctx) ToggleTrunk(ctx.entity) end } },
})
Field Type Description
label text Name shown on the label
icon text Icon of the point: a built-in icon or one registered with registerIcon
options list What the player can do (see options)
distance number Meters up to which the point is visible. Default: Config.Points.distance (2.0)
offset vector3 Where the point goes, relative to the entity. Default: the center of its model
bones text or list Bone names of the entity, one point per bone ('boot', 'bonnet', 'door_dside_f'…)
reveal text When the point shows: 'always' (when close), 'look' (only when looked at) or 'key' (only while holding the reveal key, Alt)
easy boolean Gets focus just by looking towards it, without aiming at the point
aimBoost number Makes the point easier to focus: 2 counts as twice as close to the crosshair
highlight text or false 'outline', 'box', 'both' or false. The game outline is only drawn on objects
grow boolean The object grows a little while it is looked at
moving boolean The entity moves: its point is recalculated every frame. Peds and vehicles already do
inVehicle boolean Also shows from inside a vehicle (by default nothing shows from a vehicle)
badge text Short text next to the name: a price, an amount…
status table Tag next to the name: { ok = false, label = 'Out of service' }. Green or red
disabled true or text The whole interaction is disabled. With a text, its keys turn red with that reason
data any Anything you want back in ctx.data
Bone Where the point goes
door_dside_f, door_pside_f, door_dside_r, door_pside_r The handle of that door. It moves with the door when it opens
boot The edge of the trunk. It rises with the lid when it opens
bonnet The edge of the hood. It rises with the hood when it opens
Any other bone The bone itself (petroltank, wheel_lf…)

Each option is a key on the label, or a line of the menu.

Field Type Description
label text Text of the option (required)
key text One of Config.Keys ('E', 'G', 'H'). Without it, the option takes the first free key
onSelect function Runs when the key is pressed: function(ctx) ... end
event text Client event triggered with ctx
serverEvent text Server event triggered with ctx (without the entity handle, with netId if it is networked)
canInteract function function(ctx) return true end, or return false, 'reason' (see conditions)
groups text or table Jobs or gangs that can use it: 'police', { 'police', 'ambulance' } or { police = 2 } (minimum grade)
items text or table Items the player must carry: 'lockpick', { 'lockpick', 'gloves' } or { water = 2 } (amount)
disabled true or text The option can’t be used. With a text, the key turns red with that reason when pressed
showDisabled boolean When a condition fails, the key is shown dimmed instead of hidden
hold number Milliseconds the key must be held. The key fills up while it is held
confirm true or text The first press asks ('Sell it? Press again'), the second one does it
options list A submenu
quantity true or table Choose an amount with the mouse wheel first: { min = 1, max = 10, step = 1, default = 1 }. The choice arrives in ctx.quantity
price number Price shown next to the option. With quantity, the total is shown while choosing
description text Help text under the option, in menus
badge text Short text next to the option
image text or list Picture of the option: a file of your resource, nui://, https:// or data:image/...
itemImage text or list Picture of an item from the server’s inventory, by item name

An option needs onSelect, event, serverEvent or options, otherwise it does nothing.

Files of your own resource (icons and images) must be listed in files { } of your fxmanifest.lua.

onSelect, canInteract and event receive a ctx table:

Field Description
id Id of the interaction
key Key or menu entry that was used
entity Entity handle (entities only)
netId Network id, if the entity is networked
coords Position of the entity or the point
distance Distance from the player
model Model of the entity
bone Bone of the point, if it has one
quantity Amount chosen, in options with quantity
data The data given when adding the interaction

A server event receives the same table without entity and distance:

RegisterNetEvent('shop:server:buy', function(ctx)
local src = source
-- ctx.netId, ctx.coords, ctx.quantity, ctx.data...
end)

When an interaction has more options than keys, or any option has options (submenu) or quantity, the label becomes a menu. The mouse wheel chooses, the first key (E) uses the chosen option, and the second key (G) or Backspace goes back. Keys given with key are ignored inside a menu.

SI:addModel('prop_vend_soda_01', {
label = 'Vending machine', icon = 'product',
options = {
{
label = 'Drinks', description = 'Cold drinks.',
options = {
{ label = 'Water', price = 2, itemImage = 'water', quantity = { min = 1, max = 5 },
serverEvent = 'vending:server:buy', },
{ label = 'Cola', price = 3, itemImage = 'kurkakola', serverEvent = 'vending:server:buy' },
},
},
{ label = 'Kick it', hold = 1000, onSelect = function() return false, 'Nothing falls out' end },
{ label = 'Smash the glass', confirm = 'Smash it? Press again', onSelect = function() SmashGlass() end },
},
})

The fastest conditions are the ones SL-Interact checks by itself, without calling your script: distance, groups, items, disabled and inVehicle. Use them whenever you can.

canInteract is for everything else. It is only called when the player focuses the interaction, every 500 ms while it stays focused and when the key is pressed. Never for interactions that are nearby but not looked at.

canInteract returns What the player sees
true The key shows normally
false The key does not show and pressing it does nothing
false, 'reason' The key does not show. If pressed, it turns red for a moment with the reason
false, 'reason' with showDisabled = true The key is dimmed from the start and turns red with the reason when pressed

For what only your script or the server knows (money, stock, permissions):

  • onSelect can return false, 'reason': the key turns red with the reason.
  • Client: exports.sl_interact:deny(key, reason).
  • Server: exports.sl_interact:deny(source, key, reason). If the player is no longer looking at anything, it shows as a normal notification.
-- Server: refuse after checking the money
RegisterNetEvent('shop:server:buy', function(ctx)
local src = source
if not RemoveMoney(src, 50) then
exports.sl_interact:deny(src, ctx.key, 'Not enough money')
return
end
end)
Export Description
update(id, fields) Changes any field of an interaction: label, options, badge, status, disabled…
updateMany({ [id] = fields }) Several at once. Returns how many were changed
remove(id) or remove({ ids }) Removes one or several
removeMany(ids) Removes a list of ids
removeLocalEntity(entity) Removes your interactions on that entity
removeEntity(netId) Removes your interactions on that networked entity
removeModel(models) Removes your interactions on those models
-- The label of a door changes with its state
SI:update(doorId, { options = {
{ key = 'E', label = open and 'Close' or 'Open', onSelect = ToggleDoor },
} })

Adding hundreds of interactions one by one crosses from your resource to SL-Interact hundreds of times. addMany does it in one call. Each entry says where it goes with one of these fields: coords, entity, netId, model / models, or global ('vehicle', 'ped', 'object' or 'player').

local ids = SI:addMany({
{ coords = vec3(25.7, -1347.3, 29.5), label = 'Shelf 1', options = { ... } },
{ coords = vec3(26.4, -1347.3, 29.5), label = 'Shelf 2', options = { ... } },
{ model = 'prop_atm_01', label = 'ATM', options = { ... } },
})
-- ids comes back in the same order (false for the invalid ones)
SI:updateMany({ [ids[1]] = { badge = 'x3' }, [ids[2]] = { disabled = 'Empty' } })
SI:removeMany(ids)

Built-in icons: product, door, checkout, pay, sale, pc, box, lock, unlock, build, cash, person, car, hand, gear, talk.

Register your own with registerIcon(name, image) before using it:

-- A file of your resource (listed in files { } of your fxmanifest)
SI:registerIcon('star', 'star.svg')
-- Or: 'nui://resource/file.png', 'https://...', 'data:image/png;base64,...'

When SL-Interact stops, it forgets every interaction it was given. On a live server, add yours again when it starts:

AddEventHandler('onClientResourceStart', function(name)
if name == 'sl_interact' then AddMyInteractions() end
end)

Restarting the whole server, or your resource after SL-Interact, needs nothing else.