fix(combat): track targets with retail camera

Port ClientCombatSystem's Keep in View consumer through CameraSet's pivot-to-target boom heading, including the transformed half-metre target offset and melee/missile validity gates. Preserve the existing stateful chase damping and collision architecture, with conformance coverage and named-retail pseudocode.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-11 20:36:58 +02:00
parent 927fa7881a
commit 33cc9aa16a
5 changed files with 159 additions and 14 deletions

View file

@ -143,28 +143,31 @@ public sealed class RetailChaseCamera : ICamera
Vector3 contactPlaneNormal,
float dt,
uint cellId = 0,
uint selfEntityId = 0)
uint selfEntityId = 0,
Vector3? trackedTargetPoint = null)
{
// 1. Push velocity into 5-frame ring, get average.
PushVelocity(_velocityRing, ref _velocityCount, playerVelocity);
Vector3 avgVel = AverageVelocity(_velocityRing, _velocityCount);
// 2. Heading vector — player's facing projected onto the contact
// plane (grounded) or world XY (airborne). See ComputeHeading
// doc + retail decomp :95644-95795 for why this is facing-based
// rather than velocity-based.
Vector3 heading = ComputeHeading(
avgVel,
playerYaw + YawOffset,
isOnGround,
contactPlaneNormal,
CameraDiagnostics.AlignToSlope);
// 2. Heading vector. TrackTarget uses the pivot-to-target direction;
// otherwise this is the player's facing projected onto the contact
// plane (grounded) or world XY (airborne). See named retail
// CameraSet::TrackTarget 0x00458280 and CameraManager::UpdateCamera
// 0x00456826-0x00456915.
Vector3 pivotWorld = playerPosition + new Vector3(0f, 0f, PivotHeight);
Vector3 heading = ComputeTrackedHeading(pivotWorld, trackedTargetPoint)
?? ComputeHeading(
avgVel,
playerYaw + YawOffset,
isOnGround,
contactPlaneNormal,
CameraDiagnostics.AlignToSlope);
// 3. Orthonormal heading-frame basis.
var (forward, _, up) = BuildBasis(heading);
// 4. Target pose.
Vector3 pivotWorld = playerPosition + new Vector3(0f, 0f, PivotHeight);
float horizontal = Distance * MathF.Cos(Pitch);
float vertical = Distance * MathF.Sin(Pitch);
// viewer_offset = -horizontal along forward + vertical along up.
@ -359,6 +362,24 @@ public sealed class RetailChaseCamera : ICamera
return Vector3.Normalize(projected);
}
/// <summary>
/// Port of the LOOK_AT_OBJECT direction contribution in retail
/// CameraManager::UpdateCamera (0x00456826-0x00456915). The caller
/// supplies the transformed world-space target point, including retail's
/// local (0,0,0.5) target offset. A coincident point cannot define a
/// heading, so the ordinary player-facing path remains active.
/// </summary>
internal static Vector3? ComputeTrackedHeading(Vector3 pivotWorld, Vector3? trackedTargetPoint)
{
if (trackedTargetPoint is not Vector3 targetPoint)
return null;
Vector3 towardTarget = targetPoint - pivotWorld;
return towardTarget.LengthSquared() < 1e-8f
? null
: Vector3.Normalize(towardTarget);
}
/// <summary>
/// Build an orthonormal basis with <c>forward = heading</c>. World
/// up is <c>(0, 0, 1)</c>; if <c>heading</c> is near-parallel to it