How Integrations Work#
Tutorial Creator asks very little of the rest of your server: who the player is, whether they may open the creator, and how to show them a message. All of it lives in the open custom/ folder, so it can be changed without touching the resource.
Layer | Supported | Without it |
|---|---|---|
Framework |
| Progress is stored against the licence, the creator is ace-only, and player-bound text (job, character name) stays empty. |
Database |
| The bundled tutorials still play, but nothing can be saved and everybody watches it on every spawn. |
Notifications |
| Messages are printed to chat. |
Character & spawn screens |
| Auto-start waits for the framework to report the player as loaded. |
HUD | Any, through | Only the radar is hidden. |
Nothing needs ox_lib. Adapters are plain Lua files read at start, which is why
custom/stays readable in the escrowed build and can be edited freely.
The Framework Layer#
Config.Framework is 'auto' by default and picks the first running framework. Set it to 'qs', 'esx' or 'qb' (QBCore and Qbox share it) to pin one, or 'standalone' to switch the layer off. On 'auto', a framework that was not running yet when the resource started is detected again a few seconds later and swapped in.
custom/
├── client.lua -- picks the adapter, owns Notify()
├── server.lua -- picks the adapter, owns the admin group check
├── esx/{client,server}.lua
├── qb/{client,server}.lua -- QBCore and Qbox
├── qs/{client,server}.lua
└── standalone/{client,server}.luaEach adapter is a plain table of methods that returns itself:
Side | Method | Answers |
|---|---|---|
Client |
| Is the character loaded and in the world? Auto-start waits for this. |
Client |
| Character name, job, grade, cash, bank, citizen id, phone, date of birth, sex — for the text elements. |
Client |
| The core object and its raw player data. |
Server |
| What progress is stored against. Return |
Server |
| Whether the player is in one of |
Server |
| The core object and a player by server id. |
Adding a framework#
Copy the closest adapter
Duplicate
custom/qb/(or whichever is closest) to a new folder, for examplecustom/myfw/.Answer the methods
Change each method to call your framework. Keep every method — return
nilwhere you have nothing — and keep thereturn frameworkat the end.Pin it
lua Config.Framework = 'myfw'Auto-detection only knows the four shipped frameworks, so a new one must be named. Restart and look for
framework loaded: myfwin the console.
Notifications#
Messages such as "Your tutorial progress was reset" go through Notify() in custom/client.lua: qs-interface if it is running, then ox_lib, then chat. To use another notification resource, put it first:
-- custom/client.lua
function Notify(message, kind)
if not message or message == '' then return end
if GetResourceState('okokNotify') == 'started' then
exports['okokNotify']:Alert('Tutorial', message, 5000, kind == 'error' and 'error' or 'info')
return
end
-- ...the existing fallbacks
endHiding Your HUD#
Config.Hud holds two functions: Disable runs as the tutorial starts and Enable as it ends — also after a resource restart, so nobody is left without a HUD. Out of the box they only toggle the radar; hook your own HUD in here:
-- shared/config.lua
Config.Hud = {
Enable = function()
DisplayRadar(true)
exports['qs-interface']:ToggleHud(true)
end,
Disable = function()
DisplayRadar(false)
exports['qs-interface']:ToggleHud(false)
end,
}Both are called inside a protected call, so an export that does not exist on your server cannot break the tutorial.
Character and Spawn Screens#
With Config.Tutorial.autoStart on, the tutorial waits for the session, then the framework, then for the character and spawn screens to close, then autoStartDelay — and plays the live tutorial if this character has not seen it.
Automatic. The stock qb-multicharacter, qb-multichar, qb-spawn and qbx_spawn events are listened to, so the tutorial is parked while the character picker or spawn selector is on screen and runs after the player picks a location. Logging out to the character screen parks it again.
Automatic: auto-start waits until es_extended reports the player as loaded. Multicharacters such as qs-multicharacter can also start it themselves with StartTutorial — see below.
For a fork that does not fire the usual events, tell the tutorial when those screens are open:
-- when the character or spawn UI opens
exports['qs-tutorial']:SetSpawnBusy(true)
-- after they have spawned into the world
exports['qs-tutorial']:SetSpawnBusy(false)Starting it from your spawn script#
To start it yourself — the pattern qs-multicharacter uses — put this at the end of the spawn function, for example PostSpawnPlayer and the apartment success path in qb-spawn:
if GetResourceState('qs-tutorial') == 'started' then
exports['qs-tutorial']:SetSpawnBusy(false)
if exports['qs-tutorial']:ShouldPlay() then
exports['qs-tutorial']:StartTutorial()
end
endStartTutorial blocks until the tutorial finishes and returns whether it was completed. It returns false straight away for a character that has already seen it, so it is safe to call on every spawn. If a spawn screen must open after the tutorial, wait for it first:
if GetResourceState('qs-tutorial') == 'started' then
exports['qs-tutorial']:AwaitIdle()
endServer Details in Text Elements#
Text elements bound to the server name, Discord or website read Config.Server. Leave name empty to use sv_hostname instead.
Config.Server = {
name = 'Quasar Roleplay',
discord = 'discord.gg/quasar-store',
website = 'quasar-store.com',
}Languages#
41 locales ship in locales/: ar, bg, bn, ca, cs, da, de, el, en, es, et, fa, fi, fr, he, hi, hr, hu, id, it, ja, ko, lt, lv, ms, nl, no, pl, pt, ro, ru, sk, sl, sv, sw, th, tr, uk, ur, vi, zh. Set Config.Language and that is it.
English and Spanish are complete. The other 39 cover what people actually read — every notification, the creator's navigation, the checklist, the start screen, the element names and the media pickers — while the deeper inspector labels stay in English. A key missing from a locale falls back to English rather than printing the key, and any key you fill in is used from the next resource restart.
A new language is one more JSON file in the same folder: copy en.json, translate the values, and set Config.Language to its file name.



