fix(B.7): square indicator box + bigger pick sphere for doors/lifestones/portals + diag
Visual test surfaced three follow-ups:
1. Square box, not 1:2 rectangle.
WidthHeightRatio: 0.5 → 1.0. Retail's Vivid Target Indicator draws
a square; the earlier humanoid-aspect ratio looked wrong for
non-humanoids and didn't match retail screenshots.
2. Large flat objects (doors / lifestones / portals / corpses)
weren't selectable with the new tight 0.7 m pick sphere.
WorldPicker.Pick now takes an optional radiusForGuid callback so
the host can per-entity decide a larger radius. GameWindow's pick
site supplies a lambda that bumps to 2.0 m for any entity with
BF_DOOR (0x1000), BF_LIFESTONE (0x4000), BF_PORTAL (0x40000), or
BF_CORPSE (0x2000) set in ObjectDescriptionFlags. Default stays
at 0.7 m for humanoids and items.
3. New [B.7] pick-info diagnostic on each successful pick:
[B.7] pick-info guid=0x... itemType=0x... pwd=0x... color=(r,g,b)
Lets us verify e.g. whether a 'green NPC' really is server-side
flagged as Vendor (BF_VENDOR=0x200, retail-defined green) vs a
bug in our colour lookup. The pwd bit table is acclient.h:6431-
6463 — same flags retail's gmRadarUI::GetBlipColor branches on.
Note: textured retail-sprite corner triangles remain a B.7 follow-up
deferred per the spec. MVP uses procedural fills.
This commit is contained in:
parent
631571a6ef
commit
23cb1e9636
3 changed files with 51 additions and 8 deletions
|
|
@ -8991,13 +8991,47 @@ public sealed class GameWindow : IDisposable
|
|||
origin, direction,
|
||||
_entitiesByServerGuid.Values,
|
||||
skipServerGuid: _playerServerGuid,
|
||||
maxDistance: 50f);
|
||||
maxDistance: 50f,
|
||||
// B.7 (2026-05-15): widen the pick sphere for large flat
|
||||
// objects (doors, lifestones, portals, corpses) so their
|
||||
// visible surface stays clickable even though the entity
|
||||
// origin is a single point. 0.7 m default is fine for
|
||||
// humanoids and most items; doors / portals need ~2 m
|
||||
// to cover the doorframe.
|
||||
radiusForGuid: g =>
|
||||
{
|
||||
if (_lastSpawnByGuid.TryGetValue(g, out var s)
|
||||
&& s.ObjectDescriptionFlags is { } odf)
|
||||
{
|
||||
// BF_DOOR = 0x1000, BF_LIFESTONE = 0x4000,
|
||||
// BF_PORTAL = 0x40000, BF_CORPSE = 0x2000
|
||||
// (acclient.h:6431-6463)
|
||||
const uint LargeFlatMask = 0x1000u | 0x4000u | 0x40000u | 0x2000u;
|
||||
if ((odf & LargeFlatMask) != 0) return 2.0f;
|
||||
}
|
||||
return 0.7f;
|
||||
});
|
||||
|
||||
if (picked is uint guid)
|
||||
{
|
||||
_selectedGuid = guid;
|
||||
string label = DescribeLiveEntity(guid);
|
||||
Console.WriteLine($"[B.4b] pick guid=0x{guid:X8} name={label}");
|
||||
// B.7 (2026-05-15): one-shot per-pick diagnostic so we can
|
||||
// see exactly which ItemType + PWD bitfield bits + resolved
|
||||
// RadarBlipColor are produced for the just-picked entity.
|
||||
// Helps verify whether a "green NPC" really is flagged as
|
||||
// Vendor server-side or whether our lookup is wrong.
|
||||
uint rawItemType = 0;
|
||||
if (_liveEntityInfoByGuid.TryGetValue(guid, out var info))
|
||||
rawItemType = (uint)info.ItemType;
|
||||
uint pwdBits = 0;
|
||||
if (_lastSpawnByGuid.TryGetValue(guid, out var spawn)
|
||||
&& spawn.ObjectDescriptionFlags is { } odf)
|
||||
pwdBits = odf;
|
||||
var col = AcDream.Core.Ui.RadarBlipColors.For(rawItemType, pwdBits);
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[B.7] pick-info guid=0x{guid:X8} itemType=0x{rawItemType:X8} pwd=0x{pwdBits:X8} color=({col.R},{col.G},{col.B})"));
|
||||
_debugVm?.AddToast($"Selected: {label}");
|
||||
if (useImmediately) SendUse(guid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,10 +65,12 @@ public sealed class TargetIndicatorPanel
|
|||
|
||||
/// <summary>
|
||||
/// Box width = <see cref="EntityHeight"/> projected height ×
|
||||
/// <see cref="WidthHeightRatio"/>. Humanoids are about half as
|
||||
/// wide as tall on screen, so 0.5 is a sensible default.
|
||||
/// <see cref="WidthHeightRatio"/>. Retail's Vivid Target Indicator
|
||||
/// draws a square box — four corner triangles arranged in a square —
|
||||
/// so 1.0 = width matches height. The earlier 0.5 (humanoid-ish
|
||||
/// aspect) made the box uncomfortably narrow for non-humanoids.
|
||||
/// </summary>
|
||||
public float WidthHeightRatio { get; set; } = 0.5f;
|
||||
public float WidthHeightRatio { get; set; } = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Floor for the projected screen height (pixels). Prevents the
|
||||
|
|
|
|||
|
|
@ -89,10 +89,10 @@ public static class WorldPicker
|
|||
Vector3 origin, Vector3 direction,
|
||||
IEnumerable<WorldEntity> candidates,
|
||||
uint skipServerGuid,
|
||||
float maxDistance = 50f)
|
||||
float maxDistance = 50f,
|
||||
Func<uint, float>? radiusForGuid = null)
|
||||
{
|
||||
const float Radius = 0.7f;
|
||||
const float Radius2 = Radius * Radius;
|
||||
const float DefaultRadius = 0.7f;
|
||||
|
||||
if (direction.LengthSquared() < 1e-10f) return null;
|
||||
|
||||
|
|
@ -103,13 +103,20 @@ public static class WorldPicker
|
|||
if (entity.ServerGuid == 0u) continue;
|
||||
if (entity.ServerGuid == skipServerGuid) continue;
|
||||
|
||||
// Per-entity radius (caller-supplied) lets large flat objects
|
||||
// like doors, lifestones, and portals use a bigger sphere
|
||||
// than the 0.7 m humanoid/item default — their visible
|
||||
// surface extends well beyond their origin point.
|
||||
float r = radiusForGuid?.Invoke(entity.ServerGuid) ?? DefaultRadius;
|
||||
float r2 = r * r;
|
||||
|
||||
// Geometric ray-sphere: oc = origin - center, b = dot(oc, dir),
|
||||
// c = |oc|^2 - r^2, discriminant = b^2 - c. If discriminant < 0
|
||||
// the ray misses the sphere. Otherwise nearest intersection is
|
||||
// t = -b - sqrt(discriminant).
|
||||
var oc = origin - entity.Position;
|
||||
float b = Vector3.Dot(oc, direction);
|
||||
float c = Vector3.Dot(oc, oc) - Radius2;
|
||||
float c = Vector3.Dot(oc, oc) - r2;
|
||||
float d = b * b - c;
|
||||
if (d < 0f) continue;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue