How Integrations Work#
Crosshair Creator does not talk to your framework at all. A crosshair is a personal visual setting, so players are recognised by their license: identifier and admins by an ace. That is why the same install runs unchanged on any server:
Layer | Supported | Without it |
|---|---|---|
Framework | Any — QBCore, Qbox, ESX, ox_core, ND_Core, vRP or none | Nothing changes. |
Database |
| Players get the default cross on every join; the active crosshair, share codes and admin settings are not saved. |
Notifications | Built-in, | The built-in toast is used. |
Admin access | FiveM aces — | Nobody sees the Admin tab; the defaults apply. |
Discord | One webhook for the admin audit log | Nothing is posted. |
Notifications#
Config.NotificationType decides how messages such as "Crosshair received from …" or "Server unreachable" are shown:
Value | Shows the message with |
|---|---|
| The script's own toast, top centre, styled to match the panel. The default and the recommended choice. |
| The qs-interface notification hub. Falls back to ox_lib if qs-interface is not started. |
| qs-notify. Falls back to ox_lib if it is not started. |
|
|
| The ESX notification event. |
| Your own system, through |
-- config/main.lua
Config.NotificationType = 'custom'
Config.NotificationDuration = 5000 -- ms
-- client/custom/main.lua
function OnCustomNotify(title, description, nType, duration, icon)
-- nType is 'success' | 'error' | 'inform' | 'warning'
exports['okokNotify']:Alert(title, description, duration, nType)
endA misspelled value, or 'custom' without an OnCustomNotify, never loses the message: it is shown with ox_lib and a warning is printed in the F8 console.
Discord Audit Log#
Every change made in the Admin tab can be posted to a Discord channel as an embed, with the admin's name and server id, what changed and when. Turn it on in two places:
-- config/main.lua
Config.Admin = {
-- ...
EnableWebhookLog = true,
}
-- config/webhooks.lua
Config.Webhooks = {
AdminAuditLog = 'https://discord.com/api/webhooks/<id>/<token>',
}Embed | Sent when |
|---|---|
Feature Enabled / Disabled — green / red | A feature toggle is flipped. Shows which feature and its new state. |
Official Crosshair Updated / Cleared — blue / orange | The official crosshair is set or removed. Shows its share code. |
Server Identity Updated — purple | The server name, logo or default theme colour changes. Lists every field changed. |
The embeds are written in the language set by Config.Locale. The small avatar at the top of each message is the webhook's own — set it once in Discord under Edit Webhook. If Discord refuses a post, the server console prints the HTTP status and Discord's answer.
Hooks#
The files in client/custom/ and server/custom/ are open, not encrypted. Each one holds empty functions the script calls at the right moment — fill in the ones you need.
Client hooks#
Function | Called |
|---|---|
| Before the panel opens. Return |
| Right after the panel is shown. |
| Every time a crosshair becomes active — a preset, a library build, the builder, or an export. |
| For every message, when |
-- client/custom/main.lua — only on-duty players may open the panel (QBCore)
function OnBeforeOpen()
local PlayerData = exports['qb-core']:GetCoreObject().Functions.GetPlayerData()
if PlayerData.job and PlayerData.job.onduty then return true end
Notify('Unavailable', 'You must be on duty to open the crosshair panel.', 'warning')
return false
end
-- A sound every time a crosshair is applied
function OnApply(cfg)
PlaySoundFrontend(-1, 'SELECT', 'HUD_FRONTEND_DEFAULT_SOUNDSET', true)
endMore examples are in client/custom/examples.lua. Notify(title, description, nType, duration) is available to any of them and goes through your configured notification system.
Server hooks#
Function | Called |
|---|---|
| When a player sends a build to a nearby player. |
| After every admin change. |
server/custom/commands.lua is the place for any chat command of your own, such as one that grants crosshair admin to a player id.
Tuning the Dynamics#
How each dynamic behaviour feels is the server's choice, in config/dynamics.lua. Players decide whether a behaviour is on; the server decides how it behaves.
Config.Dynamics.FireFeedback = {
ExpandPerShot = 0.5, -- how much the gap and arms grow per shot (0.5 = 50%)
ExpandMax = 2.0, -- the cap, as a multiple of the base size
HoldDurationMs = 200, -- how long it stays open after the last shot
DecayPerMs = 0.005, -- how fast it shrinks back afterwards
RateLimitMs = 50,
PollIntervalMs = 50,
}
Config.Dynamics.LowAmmo = {
HalfMagRatio = 0.5, -- orange at or below this share of a full magazine
CriticalRounds = 5, -- red at or below this many rounds (empty included)
PollIntervalMs = 250,
}
Config.Dynamics.TargetDetect = {
PollIntervalMs = 100, -- how often the reticle checks what it is on
}Every value is clamped to a safe range when the resource starts, so a typo cannot make a check run every frame or break the maths. The values marked [ADV] in the file are about cost, not feel: lower means more responsive and slightly more CPU.
None of this runs while the player is not aiming. When they aim, only the behaviours that are switched on run, together in one shared check — and if the reticle is hidden for the current weapon, nothing runs at all.
Sharing Limits#
Everything that protects the server from share spam is in Config.Share:
Config.Share = {
DefaultRadiusM = 30, -- how far "nearby" reaches, in metres
MaxRadiusM = 100, -- the most a player can ask for; the server clamps it
CooldownMs = 2000, -- how often one player may refresh the nearby list
TTLSeconds = 60, -- how long an incoming build waits before it expires
MaxPerMinute = 5, -- builds one player may send per minute
CodeTTLDays = 30, -- how long a QSR code keeps working
MaxCodesPerLicensePerDay = 100, -- new codes one player may register per day
}Languages#
21 translations ship in locales/: Arabic (ar, right-to-left), Bulgarian, Czech, Danish, German, English, Spanish, French, Hindi, Hungarian, Italian, Japanese, Korean, Dutch, Norwegian, Portuguese, Romanian, Russian, Swedish, Turkish and Simplified Chinese (zh-CN). Config.Locale sets the language of the panel, the notifications and the Discord embeds at once.
To add a language, copy locales/en.json to locales/<code>.json, translate the values and set Config.Locale to that code. Nothing else needs touching. Any key missing from a translation falls back to English instead of showing the raw key.



