Documentation

RD Eye Contact.

Synced head tracking and real eye contact between players. Install, configure, and hook it into your server.

Overview

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.

  • Look at a player and your head tracks their head
  • Look at the world and your head follows where your camera points
  • Smooth, frame-rate independent movement with natural head limits
  • Looking at a vehicle targets the player inside it
  • Automatically disabled while dead, ragdolling, falling, swimming, in a cutscene or free aiming
  • Players can toggle it with a command or keybind, and the choice is saved
  • Server-side validation and rate limiting on every update

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.

Installation

Requirements

  • A recent FiveM server artifact
  • OneSync (the head look is synced with state bags)
  • No other dependencies and no framework needed

Steps

  1. Drop the resource folder into your resources directory and keep the folder name rd-eyecontact.
  2. Add it to server.cfg:
ensure rd-eyecontact
  1. Open config.lua and adjust anything you want. All settings are optional.
  2. Restart the server. A green start message appears in the client F8 console when the resource loads.

Make sure OneSync is enabled. Without it the state bags that carry the look direction do not sync and no heads will move.

How it works

The system has three parts:

  1. Capture. Every 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.
  2. Sync. The target is stored in a state bag (StateBagKey) and only re-sent when it changes enough. The server validates every update and clears anything invalid or spammed.
  3. Apply. Nearby clients read that target, smooth it, clamp it to natural head limits, and turn the head with TaskLookAtCoord.

Only players within Apply.Range are processed, so the cost stays low on busy servers.

Configuration reference

Everything lives in config.lua. Defaults below are the values shipped in the file.

General

KeyDefaultWhat it does
DebugfalseDraws 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.
DefaultEnabledtrueState for players who never toggled it.
StateBagKey"rdEyeContact"State bag key used for syncing. Only change it if it conflicts with another script.

Capture

Controls the player who is looking (Config.Capture).

KeyDefaultWhat it does
Interval50Milliseconds between checks. Lower is smoother but costs more. Use 75 on very busy servers.
MaxRayDistance40.0How far you can look at someone, in meters.
LookAheadDistance12.0Distance of the look point when the ray hits nothing.
CoordThreshold0.15Minimum movement of the look point before a new update is sent.
RayFlags31What the ray can hit. Do not change.
LookAtVehicleOccupantstrueLooking at a vehicle targets the player inside it.
VehicleOccupantRadius2.5How close the ray must land to an occupant's head to target them.

Apply

Controls what you see on other players (Config.Apply).

KeyDefaultWhat it does
Range30.0Only players closer than this (meters) are processed.
RefreshNearby500Milliseconds between nearby player list refreshes.
SmoothSpeed16.0Head speed. Higher is faster and snappier, lower is lazier.
TargetBone31086Bone used when someone looks at a player (head bone).
TaskDuration250How long each look command lasts, in ms.
TaskRefresh80Milliseconds between look commands. Lower is smoother.

Head limits

KeyDefaultWhat it does
Limits.MaxYaw85.0Maximum head turn left or right, in degrees.
Limits.MaxPitchUp35.0Maximum head tilt up, in degrees.
Limits.MaxPitchDown-40.0Maximum head tilt down, in degrees (negative number).

Blocked states

Set a state to true to disable head look while it is active (Config.Blocked).

KeyDefaultWhat it does
DeadtrueWhile dead.
RagdolltrueWhile ragdolling.
FallingtrueWhile falling.
CutscenetrueDuring cutscenes.
SwimmingtrueWhile underwater.
AimingtrueWhile free aiming a weapon. Only affects the looking player.
InVehiclefalseSet true to disable head look inside vehicles.

Mutual eye contact

KeyDefaultWhat it does
Mutual.EnabledtrueTurns on mutual eye contact detection and the rd-eyecontact:mutual event. Paid version only. See Events.

Server protection

KeyDefaultWhat it does
Server.MaxUpdatesPerSecond20A player sending more updates than this per second gets cleared.
Server.MaxCoordDistance120.0The look point must be within this distance of the player or it is rejected.

Commands & keybind

/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.

Exports (paid)

Paid version only. These exports are not in the free version.

These client-side exports let other scripts work with RD Eye Contact.

ExportDescription
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.

Pausing from another script

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.

Checking eye contact

local targetServerId = 12
if exports['rd-eyecontact']:IsInEyeContact(targetServerId) then
    print('Eye contact with player 12')
end

Events (paid)

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)
ParameterDescription
serverIdServer id of the other player.
isMutualtrue 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.

Free vs paid

FeatureFreePaid
Synced head tracking and real eye contactYesYes
Smooth movement and head limitsYesYes
Vehicle occupant targetingYesYes
Open config.luaYesYes
Server validation and rate limitYesYes
Exports (SetEnabled, IsEnabled, SetPaused, IsInEyeContact)NoYes
rd-eyecontact:mutual eventNoYes
Support and free updatesNoYes

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.

Troubleshooting

Nothing happens, no heads turn

OneSync is not enabled. State bags need OneSync. Also check Config.DefaultEnabled and that the player has not toggled it off with /eyecontact.

One player's head does not turn

The player may be farther than Apply.Range, or be in a blocked state (dead, ragdoll, swimming and so on).

Head turns too slowly

Increase Apply.SmoothSpeed (try 22.0) and lower Apply.TaskRefresh (try 60).

Head movement looks robotic or too snappy

Lower Apply.SmoothSpeed (try 13.0).

Head does not move inside vehicles

Check Blocked.InVehicle is false. To target someone inside a car, Capture.LookAtVehicleOccupants must be true; raise VehicleOccupantRadius slightly if needed.

Gaze gets cleared on the server

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.

Performance on a very busy server

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.