Add player camera pitch synchronization - #5315
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new pitch API/storage introduces unit ambiguity (degrees vs radians) and a few conversion/convention inconsistencies that should be clarified/fixed before merge.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends the existing player pure-sync path to forward remote players’ camera pitch (derived from the already-sent camera forward vector) to other clients, and exposes it to Lua via an optional second return value from getPedCameraRotation.
Changes:
- Added an 8-bit
SCameraPitchSyncand appended it to server→client player puresync payloads (with_NETCODE_VERSIONbump). - Client now reads/stores the synchronized pitch on
CClientPlayer. - Lua
getPedCameraRotation(ped, true)returns(yaw, pitch)while preserving the legacy single-return(yaw)behavior.
Note for merge/commit message: please include the motivation (RP head-tracking use-case), the netcode layout change (_NETCODE_VERSION bump), and how you validated with multiple clients; also ensure C++ formatting is run (e.g. ./utils/clang-format.ps1) before finalizing.
File summaries
| File | Description |
|---|---|
| Shared/sdk/version.h | Bumps netcode version due to puresync layout change. |
| Shared/sdk/net/SyncStructures.h | Adds SCameraPitchSync as 8-bit float-in-range sync type. |
| Server/mods/deathmatch/logic/packets/CPlayerPuresyncPacket.cpp | Appends pitch (derived from cam forward) to outgoing player puresync. |
| Server/mods/deathmatch/logic/net/CSimPlayerPuresyncPacket.cpp | Appends pitch for simulated puresync packets as well. |
| Client/mods/deathmatch/logic/CNetAPI.cpp | Reads synced pitch from puresync and stores it on CClientPlayer. |
| Client/mods/deathmatch/logic/CClientPlayer.h | Adds storage/accessors for latest synced camera pitch. |
| Client/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp | Extends getPedCameraRotation to optionally return pitch. |
Review details
- Files reviewed: 7/7 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| float GetCameraPitch() const { return m_fCameraPitch; } | ||
| void SetCameraPitch(float fPitch) { m_fCameraPitch = fPitch; } |
|
|
||
| const CVector& vecCamFwd = cameraMatrix.vFront; | ||
| fPitch = atan2(vecCamFwd.fZ, DistanceBetweenPoints2D(CVector(), vecCamFwd)) * (180.0f / PI); | ||
| } |
| SCameraPitchSync cameraPitch; | ||
| cameraPitch.data.fValue = atan2(m_Cache.vecCamFwd.fZ, DistanceBetweenPoints2D(CVector(), m_Cache.vecCamFwd)) * (180.0f / PI); | ||
| BitStream.Write(&cameraPitch); |
| SCameraPitchSync cameraPitch; | ||
| cameraPitch.data.fValue = atan2(vecCamFwd.fZ, DistanceBetweenPoints2D(CVector(), vecCamFwd)) * (180.0f / PI); | ||
| BitStream.Write(&cameraPitch); |
|
nice one! |
Summary
This PR adds camera pitch synchronization for remote players through player pure sync and exposes it through
getPedCameraRotation.Previously,
getPedCameraRotationonly exposed the horizontal camera rotation (yaw). The client already sends its camera orientation to the server as part of player pure sync, including the vertical camera angle, but that information was not forwarded to other clients.This change reuses the existing camera forward vector on the server, derives the pitch from it, and sends it as an 8-bit synchronized value.
The existing behavior of
getPedCameraRotationis preserved:The synchronized pitch can be requested explicitly:
This keeps existing resources backward compatible while allowing scripts to use the remote player's vertical camera direction.
Motivation
On roleplay servers, head-tracking scripts commonly need each player's camera direction to make nearby players' heads follow where they are looking, especially when looking up or down.
Since remote camera pitch is not currently exposed to scripts, resources usually have to implement their own synchronization by sending camera data to the server with
triggerServerEventand then relaying it back to nearby clients.This requires custom Lua events and additional network traffic for camera information that is already available in the engine's synchronization path.
By synchronizing and exposing the pitch natively, these resources can rely on the existing player sync instead of maintaining a separate client-to-server-to-client synchronization layer.
Implementation
SCameraPitchSyncusingSFloatAsBitsSync<8>.CClientPlayer.getPedCameraRotationwith an optional pitch return value._NETCODE_VERSIONbecause the network message layout changes.The pitch is represented in degrees in the
[-90, 90]range.Testing
Tested with multiple clients while:
Remote players correctly receive both yaw and pitch, while the original single-return-value behavior remains unchanged.
This test resource was used to verify the synchronization by making nearby remote players look in the same direction as their camera.
Network impact
This does not introduce a new packet or an additional client-to-server message.
The pitch is appended to the existing player pure sync data using 8 bits, resulting in one additional byte per synchronized player pure sync packet.