> ## Content Index
> Fetch the complete content index at: https://noctis.id/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Panduan State Bags FiveM untuk Sinkronisasi Entity
- URL: https://noctis.id/blog/fivem-state-bags/
- Published: 2026-10-18T21:36:24.000Z
- Updated: 2026-10-18T21:36:24.000Z
- Description: Pelajari state bags FiveM: cara menyimpan dan menyinkronkan data pada player, entity, dan global state, mendengarkan perubahan dengan change handler, serta aturan keamanan agar tidak bisa disalahgunakan client.
- Author: Noctis
- Tags: FiveM, FiveM Development

Bayangkan kamu membuat sistem kendaraan dengan bahan bakar. Server tahu sisa bensin setiap mobil, tapi semua pemain di sekitar mobil itu juga perlu tahu, misalnya untuk menampilkan indikator di HUD atau mencegah mesin menyala saat tangki kosong. Cara lama adalah mengirim event bolak-balik ke setiap pemain. Cara yang lebih rapi di FiveM modern adalah **state bags**.

State bags adalah penyimpanan key-value yang melekat pada sebuah objek (entity, player, atau global) dan bisa direplikasi otomatis dari server ke client. Panduan ini membahas cara memakainya, cara mendengarkan perubahan, dan yang paling penting: cara memakainya dengan aman.

Dokumentasi resminya ada di [docs.fivem.net](https://docs.fivem.net/docs/scripting-manual/networking/state-bags/?ref=noctis.id). Artikel ini melengkapinya dengan contoh praktis dan pola yang umum dipakai di server roleplay.

## Prasyarat: OneSync

State bags untuk entity dan player membutuhkan **OneSync**. Hampir semua server roleplay modern sudah menjalankannya, tapi pastikan server.cfg kamu berisi:

```text
set onesync on
```

Atau setara melalui pengaturan txAdmin. Entity yang ingin diberi state juga harus merupakan *networked entity*, yaitu entity yang disinkronkan lewat jaringan. Objek lokal yang dibuat hanya di satu client tidak punya state bag yang tersinkron.

## Tiga jenis state bag

| Jenis  | Akses di server      | Akses di client                  | Contoh penggunaan                                 |
| ------ | -------------------- | -------------------------------- | ------------------------------------------------- |
| Global | GlobalState          | GlobalState                      | Cuaca, status event kota, jumlah polisi on duty   |
| Player | Player(source).state | LocalPlayer.state (diri sendiri) | Status duty, sedang diborgol, status pingsan      |
| Entity | Entity(entity).state | Entity(entity).state             | Bahan bakar kendaraan, kunci mobil, kondisi pintu |

## Menulis dan membaca state

### Di server

```lua
-- Global: dibaca semua client
GlobalState.policeOnDuty = 0

-- Player
local function setDuty(src, onDuty)
    Player(src).state:set('onDuty', onDuty, true)
end

-- Entity: misalnya saat kendaraan di-spawn dari garasi
local veh = CreateVehicle(`sultan`, 215.0, -810.0, 30.7, 160.0, true, true)
while not DoesEntityExist(veh) do Wait(0) end
Entity(veh).state:set('fuel', 65.0, true)
```

Parameter ketiga `true` pada `:set(key, value, replicated)` berarti nilai itu dikirim ke client. Kalau `false`, nilai hanya ada di server. Ini berguna untuk data internal yang tidak perlu (atau tidak boleh) dilihat pemain.

Penulisan langsung seperti `Entity(veh).state.fuel = 65.0` juga bisa dipakai dan di server otomatis direplikasi. Memakai `:set` membuat niat kamu lebih jelas saat membaca kode.

### Di client

```lua
local veh = GetVehiclePedIsIn(PlayerPedId(), false)
if veh ~= 0 then
    local fuel = Entity(veh).state.fuel
    print(('Bensin: %.1f%%'):format(fuel or 0))
end

if LocalPlayer.state.onDuty then
    print('Kamu sedang on duty')
end

print('Polisi on duty:', GlobalState.policeOnDuty)
```

Membaca state bag di client tidak memicu request jaringan. Nilainya sudah ada di memori lokal setelah direplikasi, jadi relatif murah dibanding memanggil callback ke server.

## Mendengarkan perubahan dengan change handler

Kekuatan utama state bags adalah kamu bisa bereaksi saat nilainya berubah, tanpa loop polling. Gunakan `AddStateBagChangeHandler`:

```lua
-- client.lua
AddStateBagChangeHandler('fuel', nil, function(bagName, key, value, _reserved, replicated)
    local entity = GetEntityFromStateBagName(bagName)
    if entity == 0 then return end

    if value and value <= 0.0 then
        SetVehicleEngineOn(entity, false, true, true)
    end
end)
```

Parameter pertama adalah key yang difilter (`'fuel'`), parameter kedua filter nama bag (`nil` berarti semua bag). Nama bag berbentuk string seperti `entity:123` atau `player:5`. Fungsi `GetEntityFromStateBagName` dan `GetPlayerFromStateBagName` mengubahnya kembali menjadi handle yang bisa dipakai.

Ada satu jebakan: handler bisa terpanggil ketika entity belum benar-benar ada di client, misalnya entity di luar jarak culling. Karena itu selalu cek hasil `GetEntityFromStateBagName` sebelum memakainya, dan jangan menganggap entity pasti valid.

Contoh untuk memperbarui HUD saat status duty berubah:

```lua
local myBag = ('player:%d'):format(GetPlayerServerId(PlayerId()))

AddStateBagChangeHandler('onDuty', myBag, function(_, _, value)
    SendNUIMessage({ action = 'duty', data = value == true })
end)
```

Kalau kamu membangun HUD dengan React, pola ini cocok dipadukan dengan tutorial [NUI FiveM dengan React dan TypeScript](https://noctis.id/blog/fivem-nui-react-typescript/).

## Keamanan: siapa yang boleh menulis?

Ini bagian yang paling sering diremehkan. Client **juga bisa** menulis ke state bag tertentu, misalnya state bag dirinya sendiri atau entity yang ia kontrol, dan perubahan dengan `replicated = true` dikirim ke server. Artinya cheater bisa mencoba mengubah nilai seperti `fuel` atau `onDuty` dari client.

Aturan praktisnya:

1. **State yang menentukan hak atau uang harus ditulis server.** Contoh: status polisi, kepemilikan kunci, saldo. Server adalah sumber kebenaran.
2. **Jangan percaya state yang bisa ditulis client.** Saat server memutuskan sesuatu (misalnya membuka akses gudang senjata), validasi dari data server sendiri, bukan dari state bag yang mungkin sudah diubah client.
3. **Pasang handler di server untuk key sensitif.** Handler di server terpanggil saat client mengubah state. Kamu bisa mencatat (log) atau mengembalikan nilainya.

```lua
-- server.lua: kembalikan perubahan onDuty yang datang dari client
AddStateBagChangeHandler('onDuty', nil, function(bagName, key, value, _reserved, replicated)
    if not replicated then return end -- perubahan dari server sendiri, abaikan
    local src = GetPlayerFromStateBagName(bagName)
    if src == 0 then return end
    print(('[anti-tamper] %s mencoba mengubah onDuty'):format(GetPlayerName(src)))
    -- tulis ulang nilai dari sumber data server di sini
end)
```

Perilaku persis parameter `replicated` di handler server dan opsi pembatasan penulisan oleh client bisa berbeda antar versi FXServer. Cek dokumentasi state bags dan changelog artifact yang kamu pakai, lalu uji perilakunya sendiri di server staging sebelum mengandalkannya untuk anti-cheat.

## Kapan state bags tepat dipakai?

### Cocok untuk

- Data kecil yang melekat pada entity atau player dan perlu diketahui banyak client: bahan bakar, status kunci, sirine, status diborgol.
- Status global yang jarang berubah: jumlah polisi on duty, mode event, jam tertentu.
- Menggantikan pola "broadcast event ke semua pemain setiap kali ada perubahan".

### Kurang cocok untuk

- **Data besar**, seperti seluruh isi inventory atau riwayat transaksi. Setiap perubahan dikirim lewat jaringan, dan server punya batas laju (rate limit) untuk perubahan state bag. Simpan data besar di server dan kirim ke client yang butuh saja.
- **Data rahasia** yang tidak boleh dilihat pemain lain. Data player yang direplikasi bisa dibaca client lain yang mengenal player tersebut. Gunakan `replicated = false` atau event yang dikirim ke satu client.
- **Data yang berubah sangat sering**, misalnya posisi atau kecepatan per frame. Ini sudah disinkronkan oleh game sendiri.
- **Data persisten**. State bag hilang saat entity dihapus atau server restart. Simpan ke database jika perlu bertahan.

## Contoh lengkap: kunci kendaraan sederhana

```lua
-- server.lua
RegisterNetEvent('kunci:server:toggle', function(netId)
    local src = source
    local veh = NetworkGetEntityFromNetworkId(netId)
    if veh == 0 or not DoesEntityExist(veh) then return end

    -- Validasi di server: pemain harus dekat dan pemilik kendaraan.
    local dist = #(GetEntityCoords(GetPlayerPed(src)) - GetEntityCoords(veh))
    if dist > 10.0 then return end
    if Entity(veh).state.owner ~= Player(src).state.citizenid then return end

    local locked = not Entity(veh).state.locked
    Entity(veh).state:set('locked', locked, true)
    SetVehicleDoorsLocked(veh, locked and 2 or 1)
end)
```

Di contoh ini, `owner` dan `citizenid` diasumsikan ditulis oleh server saat kendaraan di-spawn dan saat pemain login. Client hanya mengirim permintaan, sedangkan server yang memvalidasi dan menulis state.

Di sisi client, handler `locked` bisa memutar suara atau animasi lampu sein untuk semua pemain di sekitar. Jika kamu memakai QBCore, data seperti citizenid sudah tersedia di objek player QBCore. Lihat [tutorial job custom QBCore](https://noctis.id/blog/qbcore-custom-job/) untuk cara mengakses data pemain dengan benar.

## Debugging dan performa

- Print nilai di handler untuk memastikan perubahan benar-benar diterima client.
- Kalau server terasa berat setelah menambah state bags, cek apakah ada script yang menulis state di loop yang terlalu cepat. Gunakan [resmon dan profiler](https://noctis.id/blog/fivem-resmon-profiler/) untuk menemukannya.
- Peringatan rate limit atau hitch setelah perubahan besar adalah tanda kamu menulis terlalu sering atau terlalu besar. Panduan membaca peringatan itu ada di [cara mengatasi hitch warning](https://noctis.id/blog/fivem-server-hitch-warning/).

## Ringkasan

- State bags = key-value pada global, player, atau entity, bisa direplikasi ke client.
- Butuh OneSync dan networked entity.
- Gunakan `AddStateBagChangeHandler` daripada loop polling.
- Server menulis state penting, client tidak dipercaya.
- Simpan data kecil saja. Data besar, rahasia, atau persisten tetap di server dan database.

## Bangun dan uji di server sendiri

Perilaku replikasi paling baik dipelajari dengan mencobanya langsung di server dengan dua client. Kalau kamu butuh server development dengan root penuh, lihat [hosting FiveM Noctis](https://noctis.id/id/fivem-hosting/?ref=noctis.id) yang berjalan di AMD Ryzen, DDR5, dan NVMe di Singapore. Untuk perbandingan paket, kunjungi [Noctis Cloud](https://noctis.id/id/cloud/?ref=noctis.id).