> For the complete documentation index, see [llms.txt](https://qsk-development.gitbook.io/untitled/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://qsk-development.gitbook.io/untitled/qsk-ainpc.md).

# QSK AINPC

## qsk\_ainpc

Voice-driven AI NPC conversations for FiveM. Players speak naturally near configured NPCs, OpenAI transcribes the microphone audio and generates an in-character reply, then ElevenLabs or OpenAI returns spatial speech from the NPC. The release configuration contains no server-specific NPCs, coordinates, personalities, voice IDs, or API credentials.

### Features

* Unlimited configurable NPCs with individual models, positions, animations, personalities, and languages
* Natural microphone conversations gated by the active in-game voice transmission
* ElevenLabs and OpenAI text-to-speech support
* Any ElevenLabs voice through a configurable voice ID
* Spatial NPC audio heard by nearby players
* Configurable AI badge text, background color, star color, text color, height, and view distance
* Per-NPC spoken language with automatic-language mode
* Server-authoritative distance checks, upload limits, cooldowns, and request rate limits
* No response subtitles or intrusive conversation popup
* ESX, QBCore, Qbox, and standalone compatibility
* Mumble, pma-voice, mumble-voip, SaltyChat, TokoVOIP, and custom voice integration
* Asset Escrow-ready structure with open configuration, locales, UI, and documentation

### Requirements

* A current FXServer build with OneSync enabled
* A working FiveM microphone
* An OpenAI API key for transcription and AI replies
* An ElevenLabs API key when `tts.provider` is `elevenlabs`

No database, npm installation, inventory, target, or framework dependency is required.

### Framework compatibility

The resource does not access framework player objects, money, jobs, inventory, or database tables. It therefore runs natively on `qbox`, `qbcore`, `esx`, and `standalone`.

Use `"framework": "auto"` for automatic detection. A specific value may be selected for clearer startup logging.

### Voice compatibility

Set `compatibility.voice.provider` to one of these values:

| Value       | Integration                                                |
| ----------- | ---------------------------------------------------------- |
| `auto`      | Detects SaltyChat or TokoVOIP; otherwise uses FiveM Mumble |
| `mumble`    | FiveM default voice, pma-voice, and mumble-voip            |
| `saltychat` | Uses the `SaltyChat_TalkStateChanged` event                |
| `tokovoip`  | Uses the local `voip:talking` state                        |
| `custom`    | Uses the provided local event or export                    |

For SaltyChat, enable talk-state requests in its configuration. The resource names used for automatic detection can be changed in `config/config.json`.

Custom voice resources can update the talking state with either method:

```lua
TriggerEvent('qsk_ainpc:client:setVoiceTalking', true)
TriggerEvent('qsk_ainpc:client:setVoiceTalking', false)
```

```lua
exports['qsk_ainpc']:setVoiceTalking(true)
exports['qsk_ainpc']:setVoiceTalking(false)
```

### Installation

1. Place the folder in your resources directory and keep its name exactly `qsk_ainpc`.
2. Add the API keys to `server.cfg`. Never put keys in `config/config.json`.

```cfg
set qsk_ainpc_openai_key "YOUR_OPENAI_API_KEY"
set qsk_ainpc_elevenlabs_key "YOUR_ELEVENLABS_API_KEY"
```

3. Start the voice resource before `qsk_ainpc`.

```cfg
ensure pma-voice
ensure qsk_ainpc
```

4. Add at least one NPC to the empty `npcs` array in `config/config.json` using the example below.
5. Fully restart FXServer after adding or changing API-key convars. Restarting only the resource does not reload newly edited `server.cfg` values.
6. Confirm the console reports the detected framework and active TTS provider.

### Configuration

All buyer-editable settings are in `config/config.json`. Restart `qsk_ainpc` after changing this file.

#### Framework and voice

```json
"compatibility": {
  "framework": "auto",
  "voice": {
    "provider": "auto",
    "saltyChatResource": "saltychat",
      "tokoVoipResource": "tokovoip_script",
      "pushToTalkControl": 249,
      "stateSyncIntervalMs": 200
  }
}
```

#### AI badge and view range

```json
"npcLabel": {
  "enabled": true,
  "viewDistance": 20.0,
  "height": 1.2,
  "updateIntervalMs": 33,
  "text": "AI",
  "backgroundColor": { "r": 66, "g": 180, "b": 207 },
  "starColor": { "r": 255, "g": 255, "b": 255 },
  "textColor": { "r": 255, "g": 255, "b": 255 }
}
```

* `viewDistance`: maximum game-world distance at which the badge is visible
* `height`: badge height above the configured NPC position
* `backgroundColor`: RGB color of the rounded badge
* `starColor`: RGB color of the star icon
* `textColor`: RGB color of the `AI` text

RGB values must be between `0` and `255`.

#### Adding an NPC

The release ships with an empty `npcs` array so buyers never inherit demonstration characters or coordinates. Add an object like this:

```json
{
  "name": "Information Clerk",
  "model": "a_m_m_business_01",
  "coords": {
    "x": 0.0,
    "y": 0.0,
    "z": 72.0,
    "w": 0.0
  },
  "idleScenario": "WORLD_HUMAN_STAND_IMPATIENT",
  "language": "en",
  "systemPrompt": "You are a helpful information clerk in Los Santos. Stay in character and keep spoken replies concise."
}
```

Replace the example coordinates before starting the resource. `w` is the NPC heading. The server validates every conversation against the configured position.

#### NPC spoken language

`conversation.defaultLanguage` is used when an NPC has no language override. Each NPC can define its own `language`.

Supported values:

`auto`, `en`, `pl`, `de`, `fr`, `es`, `pt`, `pt-BR`, `it`, `nl`, `cs`, `uk`, `ru`, `tr`, `sv`

* `auto`: understands the player and replies in the same language
* A language code: improves transcription for that language and forces replies in it
* `pt-BR`: uses Brazilian Portuguese replies

The configured ElevenLabs model must support the selected spoken language. `eleven_multilingual_v2` is the default.

#### TTS providers and voices

```json
"tts": {
  "provider": "auto",
  "elevenlabs": {
    "voiceId": "YOUR_ELEVENLABS_VOICE_ID",
    "modelId": "eleven_multilingual_v2",
    "outputFormat": "mp3_44100_128",
    "stability": 0.55,
    "similarityBoost": 0.78,
    "style": 0.15,
    "useSpeakerBoost": true
  }
}
```

* `elevenlabs`: always use ElevenLabs and report an error if its key, voice ID, or request fails
* `openai`: use the OpenAI TTS model and voice configured under `openai`
* `auto`: use ElevenLabs when both its key and a valid voice ID are configured; otherwise use OpenAI

Paste any voice ID available to the configured ElevenLabs account into the global `voiceId`. Leave it empty when using OpenAI speech. An NPC can override the global voice and voice settings with its own `elevenlabs` object. OpenAI voice names can be changed with `openai.ttsVoice`.

#### Interaction and recording

* `interaction.proximityRadius`: distance required to start a conversation
* `interaction.serverDistanceSlack`: extra server validation distance for network movement variance
* `interaction.distanceCheckMs`: NPC proximity refresh interval
* `vad.requireInGameVoice`: only record while the selected voice integration reports that the player is talking
* `vad.volumeThreshold`: microphone sensitivity
* `vad.silenceMs`: silence required to finish a voice segment
* `vad.minimumRecordingMs`: minimum accepted recording duration
* `vad.maximumRecordingMs`: maximum recording duration

#### Spatial audio

* `spatialAudio.enabled`: broadcast NPC speech to nearby players
* `spatialAudio.broadcastRadius`: maximum hearing distance
* `spatialAudio.updateIntervalMs`: position/volume update interval during playback

#### Security and limits

* `minimumSecondsBetweenRequests`: per-player cooldown
* `maximumRequestsPerMinute`: rolling request limit
* `maximumAudioBytes`: maximum decoded microphone upload
* `maximumBase64Characters`: maximum encoded NUI payload
* `maximumTranscriptCharacters`: maximum accepted transcript length
* `maximumReplyCharacters`: maximum spoken reply length

### Locales

Set the interface locale with the top-level `locale` value. Included locale files:

* English: `en`
* Polish: `pl`
* German: `de`
* French: `fr`
* Spanish: `es`
* Brazilian Portuguese: `pt-BR`
* Italian: `it`
* Dutch: `nl`
* Czech: `cs`
* Ukrainian: `uk`
* Russian: `ru`
* Turkish: `tr`
* Swedish: `sv`

The interface locale controls error notifications and manual interaction text. NPC `language` controls recognition and spoken replies.

### Asset Escrow

The manifest protects the core client and server scripts. These buyer-editable files remain open:

* `config/*.json`
* `locales/*.json`
* `ui/**`
* `README.md`

Do not add `client/main.lua` or `server/main.js` to `escrow_ignore` when creating the Tebex package.

### First test

1. Add an NPC and fully restart FXServer after configuring the API keys.
2. Confirm the console reports at least one configured NPC and `TTS provider: ElevenLabs` or `TTS provider: OpenAI`.
3. Approach the NPC and confirm the star badge appears at `viewDistance`.
4. Transmit through the configured in-game voice resource and speak one short sentence.
5. Stop transmitting and wait for spatial speech from the NPC.
6. Verify nearby players hear the NPC from its world position.

### Troubleshooting

#### `ELEVENLABS_KEY_MISSING`

Confirm the key line contains only the key inside quotes and fully restart FXServer after editing `server.cfg`.

#### `ELEVENLABS_VOICE_ID_INVALID`

Set `tts.elevenlabs.voiceId` to a voice ID from the buyer’s ElevenLabs account. When `tts.provider` is `auto`, leaving the voice ID empty intentionally selects OpenAI speech instead.

#### The NPC does not detect speech

* Confirm FiveM has microphone permission.
* Confirm `compatibility.voice.provider` matches the installed voice system.
* For SaltyChat, enable `RequestTalkStates`.
* For custom voice, call the documented event or export when transmission starts and stops.
* Set `vad.requireInGameVoice` to `false` to test the manual `E` interaction.

#### The NPC cannot hear quiet speech

Lower `vad.volumeThreshold` in small steps. Increase it when background noise starts recordings.

#### The badge does not appear

Confirm `npcLabel.enabled` is `true`, increase `viewDistance`, and verify the NPC coordinates and model are valid.

#### API requests time out

Check server connectivity to OpenAI and ElevenLabs, then increase `openai.requestTimeoutMs` if the connection is slow.

### Privacy

Microphone audio is recorded only during an active nearby conversation and sent to the server for processing. API keys remain server-side. Conversation history is held in memory and cleared when the player disconnects or the resource stops.
