← All posts FiveM

Belajar Lua FiveM dari Nol: Membuat Script Pertama

Tutorial Lua FiveM dari nol: dasar Lua, fxmanifest.lua, client dan server script, command, event, thread, dan pola keamanan dasar lewat resource pertamamu.

Hampir semua script di server roleplay FiveM ditulis dengan Lua. Kabar baiknya, Lua termasuk bahasa yang ringkas dan mudah dipelajari. Di tutorial ini kamu akan membuat resource pertamamu dari nol: memahami struktur folder, menulis fxmanifest.lua, membuat command di sisi client, berkomunikasi dengan server lewat event, dan menghindari kesalahan keamanan yang paling sering dilakukan pemula.

Kamu tidak perlu framework apa pun untuk mengikuti tutorial ini. Server dengan recipe CFX Default sudah cukup. Belum punya server? Ikuti dulu Cara Membuat Server FiveM Sendiri dari Nol.

Dasar Lua yang perlu kamu tahu

FiveM memakai Lua 5.4 (diaktifkan dengan lua54 'yes' di manifest). Berikut sintaks dasar yang akan sering kamu pakai:

-- Komentar satu baris

-- Variabel: selalu pakai local kecuali benar-benar perlu global
local nama = "Budi"
local umur = 21
local aktif = true

-- Table: satu-satunya struktur data di Lua (bisa jadi array atau object)
local pekerjaan = { "polisi", "ems", "mekanik" }   -- array, index mulai dari 1
local pemain = { nama = "Budi", uang = 500 }        -- key-value

print(pekerjaan[1])   -- polisi
print(pemain.uang)    -- 500

-- Fungsi
local function sapa(siapa)
    return "Halo, " .. siapa   -- .. untuk menggabungkan string
end

-- Percabangan
if umur >= 18 then
    print(sapa(nama))
elseif umur > 12 then
    print("Remaja")
else
    print("Anak-anak")
end

-- Perulangan
for i, job in ipairs(pekerjaan) do
    print(i, job)
end

for key, value in pairs(pemain) do
    print(key, value)
end

Dua hal yang sering membingungkan developer dari bahasa lain: index array di Lua dimulai dari 1, dan "tidak sama dengan" ditulis ~=, bukan !=.

Client, server, dan shared

Konsep paling penting di FiveM adalah pemisahan kode:

  • Client script berjalan di komputer setiap pemain. Dipakai untuk hal yang terlihat dan dirasakan pemain: UI, input, efek, spawn kendaraan lokal, dan pemanggilan native GTA.
  • Server script berjalan di server. Dipakai untuk logika yang harus dipercaya: uang, inventory, database, dan izin.
  • Shared script dimuat di keduanya. Biasanya berisi konfigurasi.

Aturan emas: client tidak bisa dipercaya. Cheater bisa menjalankan kode Lua sendiri di client dan memicu event apa pun. Semua keputusan penting harus divalidasi di server.

Langkah 1: Buat folder resource

Di folder resources/ servermu, buat struktur berikut:

resources/
└── [lokal]/
    └── noctis_belajar/
        ├── fxmanifest.lua
        ├── config.lua
        ├── client.lua
        └── server.lua

Nama folder (noctis_belajar) adalah nama resource. Folder [lokal] hanya kategori untuk merapikan resource buatanmu sendiri.

Langkah 2: Tulis fxmanifest.lua

Manifest memberi tahu FXServer file apa saja yang harus dimuat dan di mana:

fx_version 'cerulean'
game 'gta5'
lua54 'yes'

author 'Nama Kamu'
description 'Resource belajar pertama'
version '1.0.0'

shared_script 'config.lua'
client_script 'client.lua'
server_script 'server.lua'

Penjelasan lengkap setiap field ada di dokumentasi resmi Resource manifest.

Langkah 3: Konfigurasi di config.lua

Config = {}

Config.KendaraanDiizinkan = {
    adder = true,
    sultan = true,
    faggio = true,
}

Config.JedaKendaraan = 30 -- detik

Config sengaja dibuat global supaya bisa dibaca dari client maupun server.

Langkah 4: Command pertama di client

Kita buat dua command: /koordinat untuk menampilkan posisi pemain, dan /mobil untuk memunculkan kendaraan.

-- client.lua

local function notif(teks)
    BeginTextCommandThefeedPost('STRING')
    AddTextComponentSubstringPlayerName(teks)
    EndTextCommandThefeedPostTicker(false, true)
end

RegisterCommand('koordinat', function()
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local heading = GetEntityHeading(ped)
    local teks = ('x: %.2f, y: %.2f, z: %.2f, h: %.2f'):format(coords.x, coords.y, coords.z, heading)
    print(teks)
    notif(teks)
end, false)

RegisterCommand('mobil', function(_, args)
    local model = (args[1] or 'adder'):lower()

    if not Config.KendaraanDiizinkan[model] then
        return notif('Kendaraan tidak diizinkan.')
    end

    local hash = joaat(model)
    if not IsModelInCdimage(hash) or not IsModelAVehicle(hash) then
        return notif('Model tidak ditemukan.')
    end

    RequestModel(hash)
    local batas = GetGameTimer() + 5000
    while not HasModelLoaded(hash) do
        if GetGameTimer() > batas then
            return notif('Gagal memuat model.')
        end
        Wait(10)
    end

    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local veh = CreateVehicle(hash, coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false)
    SetPedIntoVehicle(ped, veh, -1)
    SetModelAsNoLongerNeeded(hash)

    notif(('Kendaraan %s dimunculkan.'):format(model))
end, false)

Beberapa hal yang perlu diperhatikan:

  • Fungsi seperti PlayerPedId, GetEntityCoords, dan CreateVehicle disebut native. Daftar lengkapnya ada di FiveM Native Reference.
  • Model harus dimuat dulu dengan RequestModel sebelum dipakai, lalu dilepas dengan SetModelAsNoLongerNeeded supaya tidak memakan memori.
  • Loop menunggu model diberi batas waktu supaya tidak berputar selamanya.

Catatan: spawn kendaraan dari client seperti ini cocok untuk belajar. Di server produksi, pembuatan entity penting sebaiknya dikendalikan dari server agar tidak bisa disalahgunakan.

Langkah 5: Komunikasi client ke server dengan event

Sekarang kita tambahkan jeda (cooldown) yang dicek di server, sehingga pemain tidak bisa memunculkan kendaraan terus-menerus. Client meminta izin ke server, server memutuskan, lalu membalas.

-- server.lua

local terakhirSpawn = {}

RegisterNetEvent('noctis_belajar:mintaMobil', function(model)
    local src = source  -- simpan source segera, nilainya bisa berubah setelah Wait

    if type(model) ~= 'string' or not Config.KendaraanDiizinkan[model] then
        return
    end

    local sekarang = os.time()
    if terakhirSpawn[src] and sekarang - terakhirSpawn[src] < Config.JedaKendaraan then
        TriggerClientEvent('noctis_belajar:hasil', src, false, 'Tunggu sebentar sebelum memunculkan kendaraan lagi.')
        return
    end

    terakhirSpawn[src] = sekarang
    print(('[noctis_belajar] %s memunculkan %s'):format(GetPlayerName(src), model))
    TriggerClientEvent('noctis_belajar:hasil', src, true, model)
end)

AddEventHandler('playerDropped', function()
    terakhirSpawn[source] = nil
end)

Lalu ubah client supaya command /mobil hanya mengirim permintaan, dan spawn dilakukan saat server menyetujui:

-- client.lua (ganti RegisterCommand 'mobil' sebelumnya)

local function spawnKendaraan(model)
    local hash = joaat(model)
    RequestModel(hash)
    local batas = GetGameTimer() + 5000
    while not HasModelLoaded(hash) do
        if GetGameTimer() > batas then return notif('Gagal memuat model.') end
        Wait(10)
    end
    local ped = PlayerPedId()
    local coords = GetEntityCoords(ped)
    local veh = CreateVehicle(hash, coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false)
    SetPedIntoVehicle(ped, veh, -1)
    SetModelAsNoLongerNeeded(hash)
end

RegisterCommand('mobil', function(_, args)
    TriggerServerEvent('noctis_belajar:mintaMobil', (args[1] or 'adder'):lower())
end, false)

RegisterNetEvent('noctis_belajar:hasil', function(ok, data)
    if not ok then return notif(data) end
    spawnKendaraan(data)
    notif(('Kendaraan %s dimunculkan.'):format(data))
end)

Perhatikan pola keamanannya: server tidak percaya nilai yang dikirim client. Tipe data dicek, model dicocokkan dengan whitelist, dan cooldown disimpan di server. Pola yang sama berlaku untuk hal yang jauh lebih sensitif seperti uang dan item.

Langkah 6: Thread dan Wait

Kadang kamu butuh kode yang berjalan berulang, misalnya mengecek apakah pemain berada di dekat suatu titik. FiveM menyediakan CreateThread:

local titik = vector3(215.76, -810.12, 30.73)

CreateThread(function()
    while true do
        local jarak = #(GetEntityCoords(PlayerPedId()) - titik)
        if jarak < 20.0 then
            DrawMarker(1, titik.x, titik.y, titik.z - 1.0, 0, 0, 0, 0, 0, 0, 1.5, 1.5, 0.5, 120, 80, 255, 150, false, false, 2, false, nil, nil, false)
            Wait(0)      -- dekat: gambar marker setiap frame
        else
            Wait(1000)   -- jauh: cukup cek sekali per detik
        end
    end
end)

Inilah sumber lag paling umum di server FiveM: loop dengan Wait(0) yang berjalan terus walaupun tidak ada yang perlu dikerjakan. Selalu sesuaikan jeda dengan kebutuhan. Cara mengukur dampaknya dibahas di Cara Menggunakan Resmon dan Profiler FiveM.

Langkah 7: Jalankan dan tes resource

Tambahkan baris berikut ke server.cfg:

ensure noctis_belajar

Atau tanpa restart server, ketik di console server atau live console txAdmin:

refresh
ensure noctis_belajar

Setelah mengubah kode, cukup jalankan restart noctis_belajar. Masuk ke server, lalu coba /koordinat dan /mobil sultan. Output print dari client muncul di console F8, sedangkan dari server muncul di console server.

Kesalahan umum pemula

  • Lupa local. Variabel global bisa bentrok dengan resource lain dan sulit di-debug.
  • Memakai source setelah Wait. Simpan dulu ke variabel lokal di baris pertama handler.
  • Mempercayai data dari client. Jangan pernah membuat event seperti beriUang(jumlah) yang langsung dieksekusi tanpa cek di server.
  • Loop Wait(0) di mana-mana. Gunakan jeda dinamis seperti contoh di atas.
  • Salah tempat file. Native client tidak tersedia di server, dan sebaliknya. Cek kolom "side" di native reference.

Langkah berikutnya

Referensi resmi yang layak kamu bookmark: Scripting Manual FiveM dan Lua 5.4 Reference Manual.

Butuh server untuk development?

Development lebih nyaman kalau kamu punya server staging terpisah dari server utama, sehingga eksperimen tidak mengganggu pemain. Hosting FiveM Noctis menyediakan VDS Mini (2 vCPU, 4 GB RAM) yang cocok untuk development dan staging, dengan core AMD Ryzen khusus, NVMe, root penuh, dan anti-DDoS. Untuk eksperimen yang lebih ringan, ada juga Cloud VPS di Noctis Cloud. Punya pertanyaan soal scripting? Gabung ke Discord Noctis.

start here

Building something with Noctis?

Tell us what you need. A real person on our team reads every message.