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:
parent
927fa7881a
commit
33cc9aa16a
5 changed files with 159 additions and 14 deletions
|
|
@ -8415,13 +8415,15 @@ public sealed class GameWindow : IDisposable
|
|||
// player visibly rises in frame without the camera
|
||||
// swinging vertically (was the symptom of using raw
|
||||
// velocity-vector heading).
|
||||
System.Numerics.Vector3? trackedCombatTarget = GetCombatCameraTargetPoint();
|
||||
_retailChaseCamera!.Update(result.RenderPosition, _playerController.Yaw,
|
||||
playerVelocity: _playerController.BodyVelocity,
|
||||
isOnGround: result.IsOnGround,
|
||||
contactPlaneNormal: _playerController.ContactPlane.Normal,
|
||||
dt: (float)dt,
|
||||
cellId: _playerController.CellId,
|
||||
selfEntityId: _playerController.LocalEntityId);
|
||||
selfEntityId: _playerController.LocalEntityId,
|
||||
trackedTargetPoint: trackedCombatTarget);
|
||||
|
||||
// Send outbound movement messages to the live server.
|
||||
if (_liveSession is not null)
|
||||
|
|
@ -12021,6 +12023,26 @@ public sealed class GameWindow : IDisposable
|
|||
return SelectClosestCombatTarget(showToast: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves retail's combat-camera target. ClientCombatSystem::
|
||||
/// UpdateTargetTracking (0x0056A950) enables CameraSet::TrackTarget only
|
||||
/// for melee/missile combat with the option enabled and a valid attack
|
||||
/// target. TrackTarget applies target-local offset (0,0,0.5).
|
||||
/// </summary>
|
||||
private System.Numerics.Vector3? GetCombatCameraTargetPoint()
|
||||
{
|
||||
if (!_persistedGameplay.ViewCombatTarget
|
||||
|| !AcDream.Core.Combat.CombatInputPlanner.SupportsTargetedAttack(Combat.CurrentMode)
|
||||
|| _selection.SelectedObjectId is not uint selected
|
||||
|| !IsLiveCreatureTarget(selected)
|
||||
|| !_entitiesByServerGuid.TryGetValue(selected, out var target))
|
||||
return null;
|
||||
|
||||
return target.Position
|
||||
+ System.Numerics.Vector3.Transform(
|
||||
new System.Numerics.Vector3(0f, 0f, 0.5f), target.Rotation);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Phase B.4b — outbound Use handler. Wires three input actions
|
||||
// (LMB click select, LMB-double-click select+use, R hotkey
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue