Synced head tracking and real eye contact between players. Install, configure, and hook it into your server.
RD Eye Contact adds synced head look between players. Wherever a player looks, other players nearby see their head turn that way, and when two players look at each other it feels like real eye contact.
It does not depend on any framework, so it works on ESX, QBCore, QBox and standalone servers.
Free version vs paid version. Both versions use the same config.lua, which is open and editable. The exports and the rd-eyecontact:mutual event described in this guide are only in the paid version. See Free vs paid.
resources directory and keep the folder name rd-eyecontact.server.cfg:ensure rd-eyecontact
config.lua and adjust anything you want. All settings are optional.Make sure OneSync is enabled. Without it the state bags that carry the look direction do not sync and no heads will move.
The system has three parts:
Capture.Interval ms the looking player's camera casts a ray. If it hits another player (or a vehicle with a player inside), that player is the target. Otherwise the point the ray hit, or a point ahead of the camera, is the target.StateBagKey) and only re-sent when it changes enough. The server validates every update and clears anything invalid or spammed.TaskLookAtCoord.Only players within Apply.Range are processed, so the cost stays low on busy servers.
Everything lives in config.lua. Defaults below are the values shipped in the file.
| Key | Default | What it does |
|---|---|---|
Debug | false | Draws a gaze line from each nearby player's head and prints server warnings. Set to false in production. |
Command | "eyecontact" | Command players use to toggle the feature. |
DefaultKey | "" | Default keybind. Empty means none, players can bind a key in settings. |
DefaultEnabled | true | State for players who never toggled it. |
StateBagKey | "rdEyeContact" | State bag key used for syncing. Only change it if it conflicts with another script. |
Controls the player who is looking (Config.Capture).
| Key | Default | What it does |
|---|---|---|
Interval | 50 | Milliseconds between checks. Lower is smoother but costs more. Use 75 on very busy servers. |
MaxRayDistance | 40.0 | How far you can look at someone, in meters. |
LookAheadDistance | 12.0 | Distance of the look point when the ray hits nothing. |
CoordThreshold | 0.15 | Minimum movement of the look point before a new update is sent. |
RayFlags | 31 | What the ray can hit. Do not change. |
LookAtVehicleOccupants | true | Looking at a vehicle targets the player inside it. |
VehicleOccupantRadius | 2.5 | How close the ray must land to an occupant's head to target them. |
Controls what you see on other players (Config.Apply).
| Key | Default | What it does |
|---|---|---|
Range | 30.0 | Only players closer than this (meters) are processed. |
RefreshNearby | 500 | Milliseconds between nearby player list refreshes. |
SmoothSpeed | 16.0 | Head speed. Higher is faster and snappier, lower is lazier. |
TargetBone | 31086 | Bone used when someone looks at a player (head bone). |
TaskDuration | 250 | How long each look command lasts, in ms. |
TaskRefresh | 80 | Milliseconds between look commands. Lower is smoother. |
| Key | Default | What it does |
|---|---|---|
Limits.MaxYaw | 85.0 | Maximum head turn left or right, in degrees. |
Limits.MaxPitchUp | 35.0 | Maximum head tilt up, in degrees. |
Limits.MaxPitchDown | -40.0 | Maximum head tilt down, in degrees (negative number). |
Set a state to true to disable head look while it is active (Config.Blocked).
| Key | Default | What it does |
|---|---|---|
Dead | true | While dead. |
Ragdoll | true | While ragdolling. |
Falling | true | While falling. |
Cutscene | true | During cutscenes. |
Swimming | true | While underwater. |
Aiming | true | While free aiming a weapon. Only affects the looking player. |
InVehicle | false | Set true to disable head look inside vehicles. |
| Key | Default | What it does |
|---|---|---|
Mutual.Enabled | true | Turns on mutual eye contact detection and the rd-eyecontact:mutual event. Paid version only. See Events. |
| Key | Default | What it does |
|---|---|---|
Server.MaxUpdatesPerSecond | 20 | A player sending more updates than this per second gets cleared. |
Server.MaxCoordDistance | 120.0 | The look point must be within this distance of the player or it is rejected. |
/eyecontact toggles the feature for the player who types it. It turns both sending and receiving on or off and shows a message on screen. The command name comes from Config.Command.
The player's choice is saved on their own computer and kept between sessions, so they do not have to toggle it again after reconnecting.
A keybind named Toggle Eye Contact is registered for every player. Set Config.DefaultKey to give it a default key (for example 'F10'), or leave it empty and players can bind a key in Settings, Key Bindings, FiveM.
Paid version only. These exports are not in the free version.
These client-side exports let other scripts work with RD Eye Contact.
| Export | Description |
|---|---|
SetEnabled(state) | Turn the feature on or off for the local player, without a notification. |
IsEnabled() | Returns true if the feature is on for the local player. |
SetPaused(source, state) | Pause or resume your own gaze broadcasting. source is any unique name for your script. |
IsInEyeContact(serverId) | Returns true while you and that player are making eye contact. |
Useful for emotes, handcuffs, cameras or phone calls, where the head should not follow the camera. The pause stays active until every script that paused it has resumed.
-- stop broadcasting my gaze
exports['rd-eyecontact']:SetPaused('my-emote-script', true)
-- start broadcasting again
exports['rd-eyecontact']:SetPaused('my-emote-script', false)
SetPaused only stops your own gaze from being sent. You still see other players' heads turning.
local targetServerId = 12
if exports['rd-eyecontact']:IsInEyeContact(targetServerId) then
print('Eye contact with player 12')
end
Paid version only. The mutual eye contact event is not in the free version.
When you look at a player and that player is looking at you, a client event fires. It fires again when the eye contact ends. This needs Config.Mutual.Enabled = true.
AddEventHandler('rd-eyecontact:mutual', function(serverId, isMutual)
if isMutual then
print('Eye contact started with ' .. serverId)
else
print('Eye contact ended with ' .. serverId)
end
end)
| Parameter | Description |
|---|---|
serverId | Server id of the other player. |
isMutual | true when eye contact starts, false when it ends. |
Ideas: show an interaction prompt only while two players make eye contact, change stress or reputation in roleplay scripts, or trigger an emote or sound when someone stares at you.
| Feature | Free | Paid |
|---|---|---|
| Synced head tracking and real eye contact | Yes | Yes |
| Smooth movement and head limits | Yes | Yes |
| Vehicle occupant targeting | Yes | Yes |
Open config.lua | Yes | Yes |
| Server validation and rate limit | Yes | Yes |
Exports (SetEnabled, IsEnabled, SetPaused, IsInEyeContact) | No | Yes |
rd-eyecontact:mutual event | No | Yes |
| Support and free updates | No | Yes |
The scripts are protected with Cfx.re Asset Escrow in both versions. Only config.lua is open. Get the full version on our Tebex store.
OneSync is not enabled. State bags need OneSync. Also check Config.DefaultEnabled and that the player has not toggled it off with /eyecontact.
The player may be farther than Apply.Range, or be in a blocked state (dead, ragdoll, swimming and so on).
Increase Apply.SmoothSpeed (try 22.0) and lower Apply.TaskRefresh (try 60).
Lower Apply.SmoothSpeed (try 13.0).
Check Blocked.InVehicle is false. To target someone inside a car, Capture.LookAtVehicleOccupants must be true; raise VehicleOccupantRadius slightly if needed.
The player is sending too many updates or an invalid look point. Raise Server.MaxUpdatesPerSecond if you lowered Capture.Interval a lot. Turn on Config.Debug to see the reason in the server console.
Set Capture.Interval to 75 and use a lower Apply.Range.
Still stuck? Contact us or ask in the release thread on the Cfx.re forum.