Lua w FiveM – od zera do pisania komend
1) Co to jest Lua i dlaczego w FiveM?
Lua to lekki język skryptowy:
- prosty składniowo (idealny na start),
- szybki,
- elastyczny (dużo rzeczy zrobisz krótkim kodem).
W FiveM Lua „gada” z grą przez tzw. natives (funkcje GTA) i eventy (zdarzenia), dzięki czemu możesz:
- reagować na komendy gracza,
- rysować UI/HUD,
- wykonywać akcje na postaci/pojeździe,
- komunikować się z serwerem i innymi graczami.
2) Gdzie piszemy Lua? (struktura resource’u)
Utwórz resource, np. resources/[local]/cf_lua_intro/ i dodaj trzy pliki:
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
name 'cf_lua_intro'
author 'Ty'
version '1.0.0'
description 'Lekcja Lua: komendy, eventy, wątki'
client_scripts {
'client.lua'
}
server_scripts {
'server.lua'
}
Pamiętaj
- client.lua – kod u gracza (rysowanie, komendy lokalne, odczyt klawiszy).
- server.lua – kod na serwerze (logika globalna, broadcasty do graczy, uprawnienia).
- Dodaj w
server.cfg:ensure cf_lua_intro
Do testów komunikatów użyj wbudowanego resource
chat(zwykle już jest włączony).
3) Budowa komendy w Lua (FiveM)
API (wspólne dla klienta i serwera)
RegisterCommand(
'nazwa', -- nazwa komendy wpisywanej na czacie
function(source, args, rawCommand) -- handler wywoływany po wpisaniu komendy
-- source: id gracza (na kliencie zwykle 0)
-- args: tablica słów po nazwie komendy { "abc", "123" }
-- rawCommand: cały tekst komendy (np. "nazwa abc 123")
end,
false -- restricted: false=każdy, true=tylko ACE
)
Najważniejsze:
argsto stringi – liczby zamieniajtonumber(args[i]).- Flaga
restrictedgdy true – komenda wymaga uprawnienia ACE (polecam na serwerze).
4) Pierwsza komenda w kliencie – linia po linii
client.lua
-- /hello: wyświetl wiadomość na czacie
RegisterCommand('hello', function(source, args, raw)
-- Wywołaj event lokalny czatu (resource "chat")
TriggerEvent('chat:addMessage', {
color = { 255, 255, 255 }, -- kolor tekstu
multiline = true,
args = { 'CF', 'Cześć! Uczę się Lua w FiveM 🚀' } -- [prefiks, treść]
})
end, false)
Co tu się dzieje?
- Rejestrujemy komendę
hello. - W handlerze odpalamy event czatu z parametrami wiadomości.
false⇒ każdy gracz może jej użyć.
Test: Wejdź na serwer → wpisz /hello.
5) Komenda z argumentami – parsowanie i walidacja
Zrobimy /announce <tekst>: gracz na kliencie wysyła treść do serwera, a serwer rozsyła wszystkim.
client.lua
-- /announce <tekst> -> wyślij do serwera, by ogłosił wszystkim
RegisterCommand('announce', function(_, args, raw)
local text = table.concat(args, ' ')
if text == nil or text == '' then
TriggerEvent('chat:addMessage', { args = { 'CF', 'Użycie: /announce <tekst>' } })
return
end
TriggerServerEvent('cf:announce', text) -- klient -> serwer
end, false)
server.lua
-- Odbierz ogłoszenie i rozgłośnij wszystkim
RegisterNetEvent('cf:announce', function(text)
local src = source -- kto wysłał
-- Prosta walidacja długości
if type(text) ~= 'string' or #text > 200 then return end
-- Do wszystkich graczy (-1)
TriggerClientEvent('chat:addMessage', -1, {
color = { 255, 200, 0 },
args = { 'OGŁOSZENIE', text }
})
end)
Krok po kroku:
- Klient zbiera tekst (
args→table.concat). - Waliduje pusty string.
TriggerServerEventwysyła na serwer.- Serwer odbiera (
RegisterNetEvent+ handler), weryfikuje i rozsyłaTriggerClientEventdo-1(wszyscy).
6) Komenda z liczbami – przykład teleportu
Zrobimy /tp <x> <y> <z> (do testów na lokalnym serwerze!)
client.lua (dopisz pod poprzednimi)
-- /tp x y z -> teleportuj lokalnego gracza
RegisterCommand('tp', function(_, args)
if #args < 3 then
TriggerEvent('chat:addMessage', { args = { 'CF', 'Użycie: /tp <x> <y> <z>' } })
return
end
-- Konwersja string -> number (zbieraj błędy)
local x = tonumber(args[1])
local y = tonumber(args[2])
local z = tonumber(args[3])
if not x or not y or not z then
TriggerEvent('chat:addMessage', { args = { 'CF', 'Błędne liczby. Przykład: /tp -268.0 -957.0 31.2' } })
return
end
local ped = PlayerPedId()
SetEntityCoordsNoOffset(ped, x + 0.0, y + 0.0, z + 0.0, false, false, false)
end, false)
Co tłumaczymy:
- Walidacja argumentów – minimalna liczba i typy.
- Natives:
PlayerPedId()(daje handle postaci),SetEntityCoordsNoOffset(...)(ustawia pozycję).
W produkcji takie komendy zabezpieczasz (nie każdy ma mieć TP).
7) Wątki (CreateThread) i „tick”
Wątki służą do czynności powtarzalnych (np. rysowanie na ekranie co klatkę).
Przykład: prosty „HUD współrzędnych” włączany/wyłączany komendą
local showCoords = false
RegisterCommand('coords', function()
showCoords = not showCoords
end, false)
CreateThread(function()
while true do
if showCoords then
local p = GetEntityCoords(PlayerPedId())
-- Rysowanie małego napisu w lewym górnym rogu
SetTextFont(0); SetTextScale(0.35, 0.35); SetTextOutline()
SetTextEntry("STRING")
AddTextComponentString(string.format("X: %.2f Y: %.2f Z: %.2f", p.x, p.y, p.z))
DrawText(0.015, 0.015)
Wait(0) -- gdy aktywne: rysuj co klatkę
else
Wait(250) -- gdy nieaktywne: śpij dłużej (oszczędzaj FPS)
end
end
end)
Ważne zasady wydajności:
- Jeżeli coś nie musi działać co klatkę, zwiększaj
Wait(...). - Gdy warunek „nieaktywny” – dawaj dłuższy
Wait, żeby nie budzić wątku niepotrzebnie.
8) Uprawnienia ACE (restricted)
Jeśli komenda w RegisterCommand ma true na końcu, wymaga ACE:
-- server.lua
RegisterCommand('helloall', function(src, args, raw)
TriggerClientEvent('chat:addMessage', -1, { args = { 'CF', 'Wiadomość dla wszystkich' } })
end, true) -- restricted
W server.cfg dopisz np.:
add_ace group.admin command.helloall allow
add_principal identifier.steam:TWÓJHEX group.admin
Dzięki temu tylko wskazani gracze (albo grupa) mogą używać tej komendy.
9) Najczęstsze pułapki (i szybkie remedia)
- Resource się nie ładuje → sprawdź ścieżkę i nazwę
fxmanifest.lua(nie.txt). - Komendy „nie istnieją” → czy resource jest ensure/start? Użyj
refresh,start,status. - Brak wiadomości czatu → czy masz włączony resource
chat? - Zawieszenia/zużycie CPU → unikaj wielu pętli
Wait(0)bez warunku; dawaj dłuższeWait. - Argsy „puste” → waliduj
#argsi typy (używajtonumber). - Brak efektu natives → upewnij się, że wywołujesz je po kliencie (nie wszystko działa na serwerze).
10) Cały przykładowy resource (sklejka)
fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
name 'cf_lua_intro'
author 'Ty'
version '1.0.0'
client_scripts { 'client.lua' }
server_scripts { 'server.lua' }
client.lua
-- /hello
RegisterCommand('hello', function(_, args, raw)
TriggerEvent('chat:addMessage', {
color = {255,255,255},
multiline = true,
args = {'CF','Cześć! Uczę się Lua w FiveM 🚀'}
})
end, false)
-- /announce <tekst> (klient -> serwer)
RegisterCommand('announce', function(_, args)
local text = table.concat(args, ' ')
if not text or text == '' then
TriggerEvent('chat:addMessage', { args = {'CF','Użycie: /announce <tekst>'} })
return
end
TriggerServerEvent('cf:announce', text)
end, false)
-- /tp x y z
RegisterCommand('tp', function(_, args)
if #args < 3 then
TriggerEvent('chat:addMessage', { args = {'CF','Użycie: /tp <x> <y> <z>'} })
return
end
local x,y,z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3])
if not x or not y or not z then
TriggerEvent('chat:addMessage', { args = {'CF','Błędne liczby. Przykład: /tp -268.0 -957.0 31.2'} })
return
end
SetEntityCoordsNoOffset(PlayerPedId(), x+0.0, y+0.0, z+0.0, false, false, false)
end, false)
-- HUD współrzędnych: /coords przełącza
local showCoords = false
RegisterCommand('coords', function() showCoords = not showCoords end, false)
CreateThread(function()
while true do
if showCoords then
local p = GetEntityCoords(PlayerPedId())
SetTextFont(0); SetTextScale(0.35,0.35); SetTextOutline()
SetTextEntry("STRING"); AddTextComponentString(string.format("X: %.2f Y: %.2f Z: %.2f", p.x,p.y,p.z))
DrawText(0.015, 0.015)
Wait(0)
else
Wait(250)
end
end
end)
server.lua
-- Broadcast ogłoszenia do wszystkich
RegisterNetEvent('cf:announce', function(text)
local src = source
if type(text) ~= 'string' or #text == 0 or #text > 200 then return end
TriggerClientEvent('chat:addMessage', -1, {
color = {255,200,0},
args = {'OGŁOSZENIE', text}
})
end)
-- Przykład komendy restricted (ACE)
RegisterCommand('helloall', function(src)
TriggerClientEvent('chat:addMessage', -1, { args = {'CF','Serwer mówi cześć!'} })
end, true)
11) Ćwiczenia (polecam zrobić od razu)
- /me <tekst> (client) – wypisz tekst nad postacią jako 3D-napis (użyj prostego DrawText3D na
GetEntityCoords+ offset). - /bring <id> (server) – przenieś wybranego gracza do admina (server sprawdza ACE, server wysyła do klienta tego gracza event „ustaw koordy”).
- /speed <kmh> (client) – jeśli siedzisz w pojeździe, ustaw jego prędkość maksymalną (konwersja km/h → m/s).
12) Co dalej?
- Przejdź do NUI (HTML/CSS/JS) i podepnij przyciski pod
RegisterNUICallback. - Naucz się korzystać z exports (wołanie funkcji z innego resource’u).
- Jeśli planujesz framework: ESX / QBCore – tam też Lua, ale z gotową warstwą „pracy/ekonomii”.
