Define custom NPCs at fixed world coordinates and map voice commands to any server event, client event, or export. Each NPC gets its own set of recognized phrases, voice settings, and an optional secret word.
Config file: shared/npc-interact/config.lua
NPCInteractConfig.enabled#| Type | Default |
|---|---|
boolean | true |
Master toggle for the NPC interact addon.
NPCInteractConfig.debug#| Type | Default |
|---|---|
boolean | false |
Enable debug logging. Set to false in production.
NPCInteractConfig.defaultRadius#| Type | Default |
|---|---|
number | 5.0 |
Default interaction radius in meters. Applied to all NPCs unless overridden per NPC.
NPCInteractConfig.defaultFirstTalk#| Type | Default |
|---|---|
boolean | false |
When true, the NPC automatically greets the player when they enter the detection radius.
NPCInteractConfig.defaultFirstTalkDelay#| Type | Default |
|---|---|
number | 0 |
Delay in milliseconds before the NPC delivers its automatic greeting.
NPCInteractConfig.defaultAnimation#Animation defaults applied to all NPCs unless overridden per NPC.
| Field | Type | Default | Description |
|---|---|---|---|
lipSync | boolean | true | Enable lip sync animation during speech |
gesture | boolean | true | Enable hand gesture animation |
returnToIdle | boolean | true | Return to idle animation after speaking |
idleScenario | string | "WORLD_HUMAN_STAND_IMPATIENT" | GTA scenario played when NPC is idle |
NPCInteractConfig.defaultAnimation = { lipSync = true, gesture = true, returnToIdle = true, idleScenario = "WORLD_HUMAN_STAND_IMPATIENT", }
NPCInteractConfig.defaultCoverPhrases#Fallback responses spoken by the NPC when the player says something that doesn't match any option. One is chosen at random.
NPCInteractConfig.defaultCoverPhrases = { "I'm not sure what you mean by that.", "Sorry, I can only help you with what I have here.", "That's not something I can do for you.", "Let me know if you need something else.", "I don't think I have that.", "Hmm, I don't understand. Try asking differently.", "That's a bit outside my expertise." }
Override per NPC using the coverPhrases field.
NPCInteractConfig.defaultExitPhrases#Voice phrases that end the conversation with any NPC. Can be overridden per NPC with exitPhrases.
NPCInteractConfig.defaultExitPhrases = { "goodbye", "bye", "see you", "see you later", "that's all", "nevermind", "never mind", "I'm leaving" }
NPCs are defined in the NPCInteractConfig.npcs array. Each entry is a table with the following fields.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier for this NPC |
name | string | Yes | Display name shown in subtitles |
model | string | Yes | GTA ped model name |
location | vector4 | Yes | World position and heading (vector4(x, y, z, heading)) |
interactionRadius | number | No | Override defaultRadius for this NPC |
firstTalk | boolean | No | Override defaultFirstTalk for this NPC |
greeting | string | No | Spoken greeting when firstTalk = true |
exitResponse | string | No | Spoken farewell when the player exits |
voice#Per-NPC TTS voice override. Omit to use the auto-detected voice from NPC config.
voice = { voice = "en-US-GuyNeural", rate = 0.95, pitch = 1.0, volume = 1.0, }
animation#Per-NPC animation override. Omit to use defaultAnimation.
animation = { lipSync = true, gesture = true, returnToIdle = true, idleScenario = "WORLD_HUMAN_STAND_MOBILE", }
spawn#Ped spawn behaviour.
| Field | Type | Default | Description |
|---|---|---|---|
persistent | boolean | true | Keep ped alive across distance/streaming |
invincible | boolean | true | Ped cannot be killed |
frozen | boolean | false | Prevent ped from moving |
blockEvents | boolean | true | Prevent GTA AI events (fights, fleeing, etc.) |
spawn = { persistent = true, invincible = true, frozen = false, blockEvents = true, }
memory#Optional per-session player memory for this NPC.
memory = { enabled = true, rememberVisits = true, -- Track visit count rememberPurchases = true, -- Track what player triggered }
drugSelling#Enable drug selling behaviour for this NPC (requires DrugSellingConfig.enabled and an eligible NPC trait).
drugSelling = { enabled = true }
Each NPC has an options array of voice-triggered interactions.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique option identifier |
phrases | string[] | Yes | Voice phrases that trigger this option |
response | string | No | NPC TTS response when this option is matched |
action | table | nil | No | Action to execute — see below |
The action field supports two sub-fields that can be used independently or together:
action = { event = { type = "server", -- "server" | "client" | nil(nil defaults to client) name = "myres:eventName", params = { item = "water", amount = 1 } }, ["function"] = function(data) -- Client-side code runs alongside the event end }
Omit action (or set it to nil) for conversation-only options that trigger no game logic.
datafields#| Field | Description |
|---|---|
data.npcId | The NPC's id string |
data.optionId | The matched option's id |
data.type | "option", "secret", or "exit" |
data.transcript | What the player said |
Any fields from event.params are also merged into data.
-- Server event only action = { event = { type = "server", name = "npc-interact:giveItem", params = { item = "water", amount = 1 } } } -- Client event(type omitted = defaults to client) action = { event = { name = "npc-interact:clientGiveItem", params = { item = "medkit", amount = 1 } } } -- Event + client-side function action = { event = { type = "server", name = "npc-interact:giveItem", params = { item = "burger", amount = 1 } }, ["function"] = function(data) TriggerEvent('chat:addMessage', { args = { 'Enjoy your meal!' } }) end } -- Function only(no event) action = { ["function"] = function(data) print("Player said:", data.transcript) end }
Each NPC can have a hidden passphrase. When a player speaks it, a special response and action fire. The secret word uses a higher similarity threshold by default so it is not accidentally triggered.
| Field | Type | Description |
|---|---|---|
enabled | boolean | Enable the secret word system for this NPC |
phrase | string | The passphrase to match |
minSimilarity | number | Match threshold (recommended: 0.80+) |
response | string | NPC TTS response on match |
action | table | Same format as option actions |
secretWord = { enabled = true, phrase = "the blue moon rises", minSimilarity = 0.80, response = "Ah, so you know the code. Come closer...", action = { event = { type = "server", name = "npc-interact:secretAction", params = { npcId = "shop_vendor_01", secretId = "special_item" } }, ["function"] = function(data) TriggerEvent('chat:addMessage', { args = { 'SECRET UNLOCKED', 'Hidden reward discovered!' } }) end } }
NPCInteractConfig.npcs = { { id = "shop_vendor_01", name = "Marcus the Vendor", model = "s_m_m_strvend_01", location = vector4(-41.41, -1860.06, 24.82, 134.34), interactionRadius = 3.0, firstTalk = true, greeting = "Hey there! Welcome to my shop.", voice = { voice = "en-US-GuyNeural", rate = 0.95, pitch = 1.0, volume = 1.0, }, animation = { lipSync = true, gesture = true, returnToIdle = true, idleScenario = "WORLD_HUMAN_STAND_MOBILE", }, options = { { id = "buy_water", phrases = { "give me water", "water please", "I want water" }, response = "Sure thing! Here's your water.", action = { event = { type = "server", name = "npc-interact:giveItem", params = { item = "water", amount = 1 } } } }, { id = "chat_weather", phrases = { "nice weather", "how's the weather", "beautiful day" }, response = "Yeah, beautiful day! Watch out for rain later though.", action = nil }, }, secretWord = { enabled = true, phrase = "the blue moon rises", minSimilarity = 0.80, response = "Ah, so you know the code. I've got something special for you.", action = { event = { type = "server", name = "npc-interact:secretAction", params = { npcId = "shop_vendor_01", secretId = "special_item" } } } }, coverPhrases = { "Sorry, I don't have that in stock.", "That's not something I sell here.", }, exitPhrases = { "goodbye", "bye", "thanks", "that's all" }, exitResponse = "Take care! Come back anytime.", memory = { enabled = true, rememberVisits = true, rememberPurchases = true, }, drugSelling = { enabled = false }, spawn = { persistent = true, invincible = true, frozen = false, blockEvents = true, }, } }
Last updated about 9 hours ago