Install & integrate
Everything you need to drop lrr_npc_chat into a FiveM server — OneSync requirement, add-a-character flow, the complete NPC schema, the server hook contract, the Bridge API, runtime exports and lifecycle events.
Install
- Drop the resource. Place
lrr_npc_chatinto yourresources/folder. - Ensure dependencies. Append to
server.cfg. - Restart. Seven demo characters spawn across the map (bar, alley, mechanic, strip club, park bench, gang corner, boardwalk hot-dog cart).
set onesync on # REQUIRED (server-side CreatePed + state bags)
ensure ox_lib
ensure ox_target # recommended (otherwise E-key prompt is used)
ensure lrr_npc_chat
If onesync is off the server-side spawn refuses to run and logs a clear error. Turn it on with set onesync on (or legacy, or infinity).
Add a character
Drop a file into config/characters/, e.g. dani_taxi.lua:
RegisterNpc({
id = 'dani_taxi',
model = 'a_m_m_business_01',
coords = vec4(900.5, -178.2, 73.1, 200.0),
scenario = 'WORLD_HUMAN_SMOKING',
speaker = 'Dani',
tag = 'Taxi Dispatcher',
mood = 'casual',
body_language = 'leaning on the cab',
target = { label = 'Talk to Dani', icon = 'fa-taxi' },
blip = { sprite = 198, color = 5, scale = 0.7, label = 'Dispatch' },
dialogue = {
greeting = { "Need a ride?", "Where to, friend?" },
topics = {
{
id = 'ride', label = 'Quick ride ($50)',
text = "Take me to Vespucci.",
tone = 'bribe', icon = 'fa-route', cost = 50,
response = "Hop in. Five minutes.",
fail_response = "Show me the fifty first.",
server = function(src)
if not Bridge.RemoveMoney(src, 'cash', 50) then return false end
Bridge.AddItem(src, 'taxi_voucher', 1)
end,
},
{ id = 'leave', label = "Not right now.", tone = 'leave', response = "Catch you later.", terminal = true },
},
},
})
Add the file to shared_scripts in fxmanifest.lua. Restart. Done.
NPC schema
Spawn
| Field | Type | Notes |
|---|---|---|
id | string | Required, unique. Used by exports, state bags, dialogue routing. |
model | string | Ped model name (a_m_m_business_01) or hash. |
coords | vec4 | vec4(x, y, z, heading) |
scenario | string | Idle scenario (default WORLD_HUMAN_STAND_MOBILE). |
freeze | bool | Freeze in place. Default false. |
blip | table | { sprite, color, scale, label }. Optional. |
Identity (NUI header)
| Field | Type | Notes |
|---|---|---|
speaker | string | Display name in the chat header. |
tag | string | Subtitle under the name (job / location / nickname). |
mood | string | friendly, neutral, grumpy, suspicious, casual, cooperative, cautious, cornered, alert, evasive, nostalgic, curious, annoyed, open, distracted, flirty. |
body_language | string | Short phrase under the mood pill. |
trust | table | { current, max, label, hostile }. Off by default. |
gta_voice_name | string | Optional speech voice (e.g. GENERIC_HI). |
Target / interaction
| Field | Type | Notes |
|---|---|---|
target.label | string | ox_target button label. |
target.icon | string | FontAwesome class (fa-comment). |
target.distance | number | meters (default 2.0). |
target.e_key_text | string | DrawText3D prompt for the E-key fallback path. |
Dialogue
dialogue = {
greeting = 'string' or { 'pool', 'of', 'strings' },
greeting_highlights = { { word = 'foo', cls = 'hl-offer' } },
opening_system_message= { type = 'default', icon = 'fa-info', text = '…' },
topics = { ... }, -- numbered (1-9) menu
topic_bar = { ... }, -- always-on T-key chip pool
}
Topic schema
Used inside topics, followups, and topic_bar.
| Field | Type | Notes |
|---|---|---|
id | string | unique within its parent menu |
label | string | choice button label |
text | string | what the player says — echoed in chat |
subtitle | string | small text under the choice |
tone | string | dialogue, bribe, tip, investigate, threaten, flirt, rob, mug, pickpocket, help_up, friendly, leave, back, provoke, beg |
icon | string | FontAwesome class |
cost | number | $ pill (cosmetic — your server hook does the charge) |
skill | string | skill chip (cosmetic) |
disabled | bool | shows as locked |
disabled_label | string | text on the lock chip |
italic | bool | italicise the player echo |
preview | list | tooltip rows: { { type='good'|'bad'|'info', icon, text } } |
response | string / list | NPC reply, or pool |
fail_response | string | NPC reply when server returns false without a message |
highlights | list | colour words in the NPC reply — classes: hl-offer, hl-threat, hl-name, hl-location, hl-warning |
player_highlights | list | same shape, applied to the player echo |
system_message | table | { type, icon, text } — types: default, warning, success, error |
terminal | bool | close the chat after the response |
topic_bar | bool | force into the chip bar (when used inside topics) |
followups | list of topics | nested sub-menu |
server | function(src, ctx) | server-side hook (see below) |
client | function(ctx) | client-side hook (anims, custom notify, ptfx) |
Server hook contract
server = function(src, ctx)
-- ctx = { npc_id, topic_path, player = { src, name, job } }
--
-- Return values:
-- nil / true → continue normally
-- false → cancel, reply with topic.fail_response
-- false, 'short message' → cancel, reply with that message
-- { response='…', followups={…}, terminal=bool, highlights={…}, system_message={…} }
-- → fully override the reply + next menu
end
The Bridge API
Bridge.* is in scope inside any server hook. Auto-detects framework + inventory on first call.
-- MONEY
Bridge.GetMoney(src, account)
Bridge.AddMoney(src, account, amount, reason)
Bridge.RemoveMoney(src, account, amount, reason)
-- accounts: 'cash' | 'bank' | 'black_money'
-- INVENTORY
Bridge.HasItem(src, item, count)
Bridge.AddItem(src, item, count, metadata)
Bridge.RemoveItem(src, item, count)
Bridge.GetItemCount(src, item)
-- IDENTITY
Bridge.GetPlayerName(src)
Bridge.GetJob(src) -- { name, label, grade }
-- NOTIFY
Bridge.Notify(src, { title, description, type, duration })
-- Detect (mostly for debug)
Bridge.DetectFramework() -- 'qbx' | 'qb' | 'esx' | 'standalone'
Bridge.DetectInventory() -- 'ox' | 'qb' | 'esx' | 'standalone'
OneSync, persistence, state bags
The server creates every NPC via CreatePed(pedType, hash, x, y, z, h, true, true) and immediately calls SetEntityOrphanMode(ped, 2) so the engine doesn't garbage-collect the entity when no player is in scope. Each ped's lrr_npc_chat:id state bag carries its npc.id to every client that ever streams the entity. Clients listen via AddStateBagChangeHandler and decorate (scenario, ox_target, blip).
See: FiveM OneSync docs and State bags docs.
Runtime API (exports)
-- Server
exports.lrr_npc_chat:AddNpc(definition) -- same schema as a character file
exports.lrr_npc_chat:RemoveNpc(npc_id)
-- Client
exports.lrr_npc_chat:OpenDialogue(npc_id) -- force-start a chat
exports.lrr_npc_chat:CloseDialogue()
exports.lrr_npc_chat:IsDialogueOpen()
exports.lrr_npc_chat:GetNpcPed(npc_id)
exports.lrr_npc_chat:IsNpcPed(ped)
exports.lrr_npc_chat:GetNpcIdFromPed(ped)
Client events
AddEventHandler('lrr_npc_chat:client:npcSpawned', function(npc_id, ped) end)
AddEventHandler('lrr_npc_chat:client:npcDespawned', function(npc_id, ped) end)
AddEventHandler('lrr_npc_chat:client:npcsLoaded', function() end)
Demo characters
| File | Where | Showcases |
|---|---|---|
mike_barkeep.lua | Pitchers Bar | random greeting pool, nested buy menus, tip flow, highlights, fail_response |
anya_fence.lua | Vespucci alley | item-gated topics (HasItem), override-from-hook unlocking a discount sub-menu |
jenkins_mechanic.lua | Premium Deluxe | job-aware discount, multi-tier upgrade tree, client anim hook |
lola_hostess.lua | Vanilla Unicorn | VIP pass + dance voucher chain, flirt tone, photo+tip topic-bar |
eli_grandpa.lua | Mirror Park bench | deep response pool (8 war stories), help-up terminal, mug + guilt system message |
carlos_sicario.lua | El Burro alley | bulk-buy followup, hostile threaten close, "respect" bribe unlock |
hank_hotdog.lua | Vespucci boardwalk | minimal lightweight vendor — 4 topics, secret menu unlock |
Compatibility
- FiveM, lua54, OneSync on
- QBX-Core / QB-Core / ESX / standalone
- ox_inventory / qb-inventory / esx_inventory
Wire any topic's server hook to Account Levels — exports.account_levels:RequireLevel(src, n) rejects with a localized notification when the player's tier is too low.