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:

  1. 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)
    
  2. Align the meta files with your ranges. The resource ships 65 weapon meta patches in metas/ (data_file "WEAPONINFO_FILE_PATCH"). If you change maxDist, the matching WeaponRange in the meta has to fit — see Weapon ranges.

Debug and performance

XSTUDIOS.debug = false    -- Enable debug prints
XSTUDIOS.blackout = true  -- Disable Lights increases Performance
OptionTypeDefaultDescription
debugbooleanfalsePrints hit and sync information to the console. Useful while tuning ranges, noisy everywhere else — keep it off in production
blackoutbooleantrueDisables 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,
    }
}
KeyDescription
defaultDistance limit used for every weapon that has no own entry
weaponsPer-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:

WeaponmaxDistWeaponRange in the shipped metaFile
WEAPON_PISTOL120.0100.0metas/weapons.meta
WEAPON_PISTOL50150.0130.0metas/weapons.meta
WEAPON_PISTOL_MK2135.0110.0metas/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,
}
OptionTypeDefaultDescription
eventstringesx:onPlayerDeathThe 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
notifybooleantrueSends the kill notification to killer and victim. Set to false if your own death system already announces kills
dimzerobooleanfalseWith 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.

MethodRuns onStrength
damageClientClose range. Reacts to the damage event the shooter's own client already registered
serverServerThe allrounder. Instant hit registration, verified server-side, so it cannot be faked by a client
weaponClientLong 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.

ValueResult
0.0No recoil at all — the example setting for WEAPON_PISTOL
0.3 – 0.6Noticeably reduced, still visible kick. Typical for a PvP-focused server
1.0Vanilla

Weapons without an entry keep their vanilla recoil.

Kill effects menu

XSTUDIOS.effectsMenu = {
    label   = "KILL EFFECTS",
    enabled = true,
    command = "effects",
    keybind = "",
    …
}
OptionTypeDefaultDescription
labelstringKILL EFFECTSHeading of the menu
enabledbooleantrueMaster switch for the menu and the kill effects themselves. With false, no effects are played at all
commandstringeffectsChat command that opens the menu, without the slash
keybindstring""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:

  1. Drop the image into html/images/ — the files { "html/**/**" } entry in fxmanifest.lua already ships anything in that folder.
  2. Add the label to killDots, e.g. "Gold".
  3. 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.

FunctionRuns onParameters
client(message)Clientmessage — the finished text, e.g. the confirmation after picking an effect
server(srcName, srcId, targetName, targetId, killDist)ServerKiller 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.