Config (config/config.lua)
X-Sync replaces GTA's unreliable hit registration with its own synced hit detection, and adds kill notifications, kill effects, per-weapon recoil overrides and patched weapon ranges on top of it.
Everything you configure lives in config/config.lua.
client_scripts { "config/config.lua", … }
server_scripts { "config/config.lua", … }
Before you start
Two things have to be done once, otherwise the resource will fight with your framework:
-
Replace
GetPedSourceOfDeath. Wherever your scripts read the killer of a ped, use the export instead — the native returns the wrong killer once hits are emulated:local killer = exports["x_sync"]:GetPedSourceOfDeath(ped) -
Align the meta files with your ranges. The resource ships 65 weapon meta patches in
metas/(data_file "WEAPONINFO_FILE_PATCH"). If you changemaxDist, the matchingWeaponRangein the meta has to fit — see Weapon ranges.
Debug and performance
XSTUDIOS.debug = false -- Enable debug prints
XSTUDIOS.blackout = true -- Disable Lights increases Performance
| Option | Type | Default | Description |
|---|---|---|---|
debug | boolean | false | Prints hit and sync information to the console. Useful while tuning ranges, noisy everywhere else — keep it off in production |
blackout | boolean | true | Disables the dynamic weapon lights (muzzle flash lighting). Costs nothing visually in a firefight and gives back frames in big fights |
Weapon ranges
XSTUDIOS.maxDist = { -- Max distance for weapons (in meters)
["default"] = 130.0,
["weapons"] = {
[`WEAPON_PISTOL`] = 120.0,
[`WEAPON_PISTOL50`] = 150.0,
[`WEAPON_PISTOL_MK2`] = 135.0,
}
}
| Key | Description |
|---|---|
default | Distance limit used for every weapon that has no own entry |
weapons | Per-weapon overrides, keyed by weapon hash |
The backtick syntax [`WEAPON_PISTOL`] is Lua 5.4 hash notation in FiveM — the compiler turns the string into its weapon hash, so you do not have to look the number up. It only works because the resource sets lua54 "yes" in its manifest.
Only weapons you actually want to deviate from default need an entry. Everything else falls back automatically.
Keeping the meta files in sync
A bullet can never travel further than the WeaponRange in its weapon meta, no matter what you put into maxDist. The shipped patches and the example config look like this:
| Weapon | maxDist | WeaponRange in the shipped meta | File |
|---|---|---|---|
WEAPON_PISTOL | 120.0 | 100.0 | metas/weapons.meta |
WEAPON_PISTOL50 | 150.0 | 130.0 | metas/weapons.meta |
WEAPON_PISTOL_MK2 | 135.0 | 110.0 | metas/weapons_pistol_mk2.meta |
So the rule is: set WeaponRange to the distance you actually want to shoot, and keep maxDist at or slightly above it. A maxDist far above the meta range does nothing — the bullet is already gone. A maxDist below it cuts off hits the game would still have allowed.
While you are in the meta, DamageFallOffRangeMin / DamageFallOffRangeMax decide where damage starts dropping off — a range increase is usually pointless if the damage falls off after 40 metres.
Aim offsets
XSTUDIOS.offsets = {
x = 0.0017,
y = 0.0025,
}
A small correction applied to the shot ray so the emulated hit lands where the crosshair is, not where the muzzle points. The defaults are tuned for the standard crosshair and should stay as they are.
Only touch them if hits land consistently off-centre on your server, and then in tiny steps — the values are in thousandths, so 0.0017 → 0.0020 is already a noticeable change.
Death event
XSTUDIOS.deathEvent = {
event = "esx:onPlayerDeath",
notify = true,
dimzero = false,
}
| Option | Type | Default | Description |
|---|---|---|---|
event | string | esx:onPlayerDeath | The death event of your framework. This is what kill notifications and kill effects hook into — set it to your own event if you are not on ESX |
notify | boolean | true | Sends the kill notification to killer and victim. Set to false if your own death system already announces kills |
dimzero | boolean | false | With true, only deaths in dimension (routing bucket) 0 are handled. Leave it false if your PvP happens in another bucket, for example an FFA arena |
Hit registration methods
XSTUDIOS.methods = {
["damage"] = true, -- client (Good for Close Range)
["server"] = true, -- server (Allrounder Instant Hitreg)
["weapon"] = true, -- weapon (Good For Long Range Shots)
}
The three methods cover different distances and run side by side — the default is to leave all of them enabled.
| Method | Runs on | Strength |
|---|---|---|
damage | Client | Close range. Reacts to the damage event the shooter's own client already registered |
server | Server | The allrounder. Instant hit registration, verified server-side, so it cannot be faked by a client |
weapon | Client | Long range shots, where the vanilla sync usually drops the hit entirely |
Switch one off only to narrow down a problem. A server that runs with server = false gives up the one check that a modified client cannot bypass.
Excluded weapons
XSTUDIOS.excluded = { -- Excluded weapon hashes that won't kill when made headshot with
[`WEAPON_UNARMED`] = true,
[`WEAPON_BAT`] = true,
[`WEAPON_STUNGUN`] = true,
[`WEAPON_FLASHLIGHT`] = true,
[`WEAPON_KNIFE`] = true,
[`WEAPON_NIGHTSTICK`] = true,
}
X-Sync turns a registered headshot into a kill. Every weapon in this list is exempt from that — a bat to the head knocks somebody out the way your framework decides, it does not instantly kill.
Melee weapons, the stungun and anything roleplay-relevant belong in here. Add your own the same way, with the hash notation and true.
Recoil
XSTUDIOS.newRecoil = { -- Weapon hash with the recoil override 1.0 is default
[`WEAPON_PISTOL`] = 0.0,
[`WEAPON_PISTOL50`] = 0.3,
[`WEAPON_PISTOL_MK2`] = 0.6,
}
Per-weapon recoil multiplier. 1.0 is the game's stock recoil, 0.0 removes it completely, everything in between scales it.
| Value | Result |
|---|---|
0.0 | No recoil at all — the example setting for WEAPON_PISTOL |
0.3 – 0.6 | Noticeably reduced, still visible kick. Typical for a PvP-focused server |
1.0 | Vanilla |
Weapons without an entry keep their vanilla recoil.
Kill effects menu
XSTUDIOS.effectsMenu = {
label = "KILL EFFECTS",
enabled = true,
command = "effects",
keybind = "",
…
}
| Option | Type | Default | Description |
|---|---|---|---|
label | string | KILL EFFECTS | Heading of the menu |
enabled | boolean | true | Master switch for the menu and the kill effects themselves. With false, no effects are played at all |
command | string | effects | Chat command that opens the menu, without the slash |
keybind | string | "" | Default key, e.g. "F7". Empty means no default binding — players can still assign one under Settings → Key Bindings → FiveM |
Kill dots
The dot is the marker the killer sees on a confirmed kill. Two tables belong together:
killDots = { -- Labels shown in the menu
"Blue", "Green", "Orange", "Pink", "Purple", "Red", "Turquoise", "Yellow", "Black", "White"
},
dotImages = { -- Files in html/images
["blue"] = "blue.png",
["green"] = "green.png",
…
},
killDots is the list of entries the player picks from, dotImages maps each one to an image file. The key is the label in lowercase, the value is a file in html/images/.
To add your own dot:
- Drop the image into
html/images/— thefiles { "html/**/**" }entry infxmanifest.luaalready ships anything in that folder. - Add the label to
killDots, e.g."Gold". - Add the matching line to
dotImages:["gold"] = "gold.png".
A label without a matching dotImages key shows up in the menu but has no image, so always change both lists together.
Kill effects
killEffects = { -- Labels shown in the menu
"Neutral", "Pink", "Orange", "Purple", "Green"
},
effectNames = { -- The particle effects that are played
["neutral"] = "PennedInOut",
["pink"] = "TinyRacerPinkOut",
["orange"] = "PPOrangeOut",
["purple"] = "PPPurpleOut",
["green"] = "PPGreenOut",
}
Same pairing as the dots: killEffects holds the labels, effectNames maps the lowercase label to the particle effect that is played on a kill.
The values are GTA screen effect names. You can use any of the game's existing ones or build your own with CodeWalker and stream it with your server.
Notifications
XSTUDIOS.notify = {
client = function(message)
TriggerEvent('x_hud:notify', "info", "EFFECTS", message, 5000)
end,
server = function(srcName, srcId, targetName, targetId, killDist)
TriggerClientEvent('x_hud:notify', srcId, "info", "KILLNOTIFY", ("Du hast %s(%s) aus %s Metern getötet"):format(targetName, targetId, killDist), 5000)
TriggerClientEvent('x_hud:notify', targetId, "info", "KILLNOTIFY", ("Du wurdest von %s(%s) aus %s Metern getötet"):format(srcName, srcId, killDist), 5000)
end
}
Both functions are examples wired up for x_hud — replace the bodies with your own notification system and nothing else changes.
| Function | Runs on | Parameters |
|---|---|---|
client(message) | Client | message — the finished text, e.g. the confirmation after picking an effect |
server(srcName, srcId, targetName, targetId, killDist) | Server | Killer name and ID, victim name and ID, and the kill distance in metres |
The server function is called once per kill and is responsible for both messages — one to the killer, one to the victim. If you only want to notify the killer, drop the second TriggerClientEvent.
The example texts are German. They are written directly into the function, not into XSTUDIOS.translation, so this is where you translate them:
server = function(srcName, srcId, targetName, targetId, killDist)
TriggerClientEvent('x_hud:notify', srcId, "info", "KILLNOTIFY",
("You killed %s(%s) from %s metres"):format(targetName, targetId, killDist), 5000)
TriggerClientEvent('x_hud:notify', targetId, "info", "KILLNOTIFY",
("You were killed by %s(%s) from %s metres"):format(srcName, srcId, killDist), 5000)
end
Set deathEvent.notify = false if you do not want kill notifications at all.
Translation strings
XSTUDIOS.translation = {
["setKillEffect"] = "Set Kill Effect to %s",
["setKillDot"] = "Set Kill Dot to %s",
}
The two confirmations shown after a player picks something in the menu. %s is replaced with the chosen label and has to stay in the text.
The skipDamage hook
XSTUDIOS.skipDamage = function(ped) -- Extra checks for the headshot damage check
-- if GetPedConfigFlag(ped, 2) then -- IF HeadShot Mode (SetPedSuffersCriticalHits)
-- return true
-- end
return false
end
Called before X-Sync applies its headshot handling to a ped. Return true to skip that ped, false to let the normal handling run.
This is the place for server-specific exceptions:
XSTUDIOS.skipDamage = function(ped)
-- Peds that already suffer critical hits are handled by the game itself
if GetPedConfigFlag(ped, 2) then
return true
end
-- No headshot kills in a safe zone
if LocalPlayer.state.safezone then
return true
end
return false
end
Keep the function cheap — it runs on the damage path, not once per minute.
Export
local killer = exports["x_sync"]:GetPedSourceOfDeath(ped)
Because hits are emulated, the native GetPedSourceOfDeath can report the wrong killer. Use this export instead in every script that evaluates a death — killfeeds, statistics, wanted systems, ambulance jobs.