--- before/addons/core/Scripts/Game/ERR/00_Core/ERR_ObserverPlayerView.c +++ after/addons/core/Scripts/Game/ERR/00_Core/ERR_ObserverPlayerView.c @@ -2,6 +2,20 @@ // spectators of that body. No client-supplied position affects gameplay. class ERR_ObserverViewRules { + // The body and camera arrive through independently timed replication streams. + // Keep the sampled world axes and world-axis eye offset together. Applying + // the proxy body's rotation to delayed body-local aim adds its yaw error to + // both aim and eye position, even when the owner's camera is steady. + static void Encode(vector body[4], vector view[4], out vector pose[4]) + { + for (int axis = 0; axis < 3; axis++) pose[axis] = view[axis]; + pose[3] = view[3] - body[3]; + } + static void Decode(vector body[4], vector pose[4], out vector view[4]) + { + for (int axis = 0; axis < 3; axis++) view[axis] = pose[axis]; + view[3] = body[3] + pose[3]; + } static bool Sample(vector position, vector forward, vector up, float fov, float nearPlane) { // Negated bounded comparisons also reject NaN/infinity before matrix math. @@ -105,13 +119,13 @@ float now = GetGame().GetWorld().GetWorldTime(); if (now - m_ERRViewRequestAt >= 500) { m_ERRViewRequestAt = now; Rpc(ERR_RequestPlayerView); } - vector local[4], world[4]; - if (!m_ERRViewBuffer.Read(m_ERRObservedId, now, seconds, local, fov, nearPlane)) return false; + vector pose[4], world[4]; + if (!m_ERRViewBuffer.Read(m_ERRObservedId, now, seconds, pose, fov, nearPlane)) return false; RplId scopeWeapon; vector scopeData; m_ERRViewBuffer.Scope(scopeWeapon, scopeData); if (scopeWeapon != RplId.Invalid() && !ERR_ObserverScope.Resolve(target, scopeWeapon, scopeData)) return false; target.GetWorldTransform(world); - Math3D.MatrixMultiply4(world, local, transform); + ERR_ObserverViewRules.Decode(world, pose, transform); return true; } [RplRpc(RplChannel.Reliable, RplRcver.Server)] @@ -157,15 +171,15 @@ PlayerCamera camera = GetPlayerCamera(); CameraManager manager = GetGame().GetCameraManager(); if (!mode || !mode.Active() || !ERR_Actors.CanFight(actor) || !camera || !manager || manager.CurrentCamera() != camera) return; - vector world[4], view[4], local[4]; + vector world[4], view[4], pose[4]; actor.GetWorldTransform(world); camera.GetWorldCameraTransform(view); float fov = camera.GetVerticalFOV(), nearPlane = camera.GetNearPlane(); RplId scopeWeapon; vector scopeData; ERR_ObserverScope.Sample(actor, view, fov, scopeWeapon, scopeData); - Math3D.MatrixInvMultiply4(world, view, local); - if (!ERR_ObserverViewRules.Sample(local[3], local[2], local[1], fov, nearPlane)) return; - Rpc(ERR_SubmitPlayerView, ++m_ERRViewSequence, local[3], local[2], local[1], fov, nearPlane, scopeWeapon, scopeData); + ERR_ObserverViewRules.Encode(world, view, pose); + if (!ERR_ObserverViewRules.Sample(pose[3], pose[2], pose[1], fov, nearPlane)) return; + Rpc(ERR_SubmitPlayerView, ++m_ERRViewSequence, pose[3], pose[2], pose[1], fov, nearPlane, scopeWeapon, scopeData); } [RplRpc(RplChannel.Unreliable, RplRcver.Server)] protected void ERR_SubmitPlayerView(int sequence, vector position, vector forward, vector up, float fov, float nearPlane, RplId scopeWeapon, vector scopeData)