From de4988054c840f9eacab16f067cef56d6e8a3a6e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 16 Jul 2026 16:06:28 +0100 Subject: [PATCH] Retain receipt-owned hit feedback --- .../EveUnityCombatPresentationRenderer.cs | 13 +++++- .../Editor/EveUnityCombatPresentationTests.cs | 44 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityCombatPresentationRenderer.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityCombatPresentationRenderer.cs index 6b0cd5b..2e3e8ae 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityCombatPresentationRenderer.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityCombatPresentationRenderer.cs @@ -62,6 +62,7 @@ namespace GameCult.Eve.UnityScene return; } + _lastPresentation = Current; var marker = FindMarker(Current.SelectedTargetEntityId); if (marker == null) { @@ -69,7 +70,6 @@ namespace GameCult.Eve.UnityScene return; } - _lastPresentation = Current; EnsureVisuals(); PresentationAxes(out var right, out var up, out var towardCamera, out var cullingMask); var visualBounds = VisualBounds(marker.transform, cullingMask); @@ -128,7 +128,18 @@ namespace GameCult.Eve.UnityScene if (presentation?.PresentsHit(receipt) != true) presentation = _lastPresentation; if (presentation?.PresentsHit(receipt) == true) + { + PresentationAxes(out var right, out var up, out _, out _); + _lastCenter = new Vector3( + (float)receipt.Endpoint.X, + (float)receipt.Endpoint.Y, + (float)receipt.Endpoint.Z); + _lastRight = right; + _lastUp = up; + _lastRadius = Math.Max(0.8f, _lastRadius); + _hasLastTarget = true; _hitUntil = Time.unscaledTime + presentation.HitMarkerDurationSeconds; + } } private EveUnityPlayableWorldEntityMarker? FindMarker(string entityId) diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCombatPresentationTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCombatPresentationTests.cs index 8a06221..065037e 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCombatPresentationTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCombatPresentationTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Reflection; using NUnit.Framework; +using UnityEngine; #nullable enable @@ -38,6 +40,37 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(combat.PresentsHit(Shot(4, 9, true, 0, 12)), Is.True); } + [Test] + public void HitMarkerRetainsAuthoritativeEndpointAfterTargetLeavesTheWorld() + { + var root = new GameObject("combat-hit-retention-test"); + try + { + var renderer = root.AddComponent(); + var presentation = new EveUnityCombatPresentation(Combat()); + typeof(EveUnityCombatPresentationRenderer) + .GetField("_lastPresentation", BindingFlags.Instance | BindingFlags.NonPublic)! + .SetValue(renderer, presentation); + typeof(EveUnityCombatPresentationRenderer) + .GetMethod("OnShotAvailable", BindingFlags.Instance | BindingFlags.NonPublic)! + .Invoke(renderer, new object[] { Shot(4, 9, true, 12, 0, "7.5,-3.25") }); + + renderer.RefreshNow(); + + Assert.That(renderer.HitMarkerVisible, Is.True, + "A destroyed target must not erase receipt-owned hit feedback."); + var line = root.transform.Find("Eve combat presentation/Hit marker A")! + .GetComponent(); + var center = (line.GetPosition(0) + line.GetPosition(1)) * 0.5f; + Assert.That(center.x, Is.EqualTo(7.5f).Within(0.0001f)); + Assert.That(center.z, Is.EqualTo(-3.25f).Within(0.0001f)); + } + finally + { + UnityEngine.Object.DestroyImmediate(root); + } + } + private static EveUnitySceneProjection Projection(params EveUnitySceneNode[] children) => new EveUnitySceneProjection("provider", "surface", "world", "commands", "receipt", "provider", null, Node("root", "surface", children)); @@ -59,7 +92,13 @@ namespace GameCult.Eve.UnityScene.Tests ["radialFillMaximum"] = "0.75" }); - private static EveUnityShotReceipt Shot(int source, int target, bool hit, double damage, double shield) => + private static EveUnityShotReceipt Shot( + int source, + int target, + bool hit, + double damage, + double shield, + string endpoint = "0,0") => new EveUnityShotReceipt(Node("shot", "shot.receipt", new Dictionary(StringComparer.Ordinal) { ["shotId"] = Guid.NewGuid().ToString("N"), @@ -67,7 +106,8 @@ namespace GameCult.Eve.UnityScene.Tests ["targetEntityIndex"] = target.ToString(), ["hit"] = hit.ToString(), ["appliedDamage"] = damage.ToString(System.Globalization.CultureInfo.InvariantCulture), - ["shieldAbsorbedDamage"] = shield.ToString(System.Globalization.CultureInfo.InvariantCulture) + ["shieldAbsorbedDamage"] = shield.ToString(System.Globalization.CultureInfo.InvariantCulture), + ["endpoint"] = endpoint })); private static EveUnitySceneNode Node(string id, string kind, params EveUnitySceneNode[] children) =>