fix(client): restore retail interaction parity
All checks were successful
CI / linux-portable (push) Successful in 3m27s
CI / windows-gate (push) Successful in 6m42s
CI / release (push) Successful in 2m12s

Harden keyboard and camera routing, inventory and vendor interactions, chat/emotes, relog portal flow, and paperdoll rendering. Add retail research, connected gate coverage, and release-gate validation.
This commit is contained in:
Erik 2026-08-26 20:45:11 +02:00
parent 0c699240e0
commit f6fe0f2a4f
151 changed files with 10162 additions and 1211 deletions

View file

@ -16,6 +16,8 @@ public sealed class ChatCommandTargetState : IDisposable
private readonly object _gate = new();
private string? _lastIncomingTellSender;
private string? _lastOutgoingTellTarget;
private string? _lastMonarchSender;
private string? _lastPatronSender;
private bool _disposed;
public ChatCommandTargetState(ChatLog chat)
@ -42,6 +44,26 @@ public sealed class ChatCommandTargetState : IDisposable
}
}
/// <summary>Most recent sender of an incoming retail <c>@m</c> broadcast.</summary>
public string? LastMonarchSender
{
get
{
lock (_gate)
return _lastMonarchSender;
}
}
/// <summary>Most recent sender of an incoming retail <c>@p</c> broadcast.</summary>
public string? LastPatronSender
{
get
{
lock (_gate)
return _lastPatronSender;
}
}
public bool IsDisposed
{
get
@ -61,6 +83,8 @@ public sealed class ChatCommandTargetState : IDisposable
{
_lastIncomingTellSender = null;
_lastOutgoingTellTarget = null;
_lastMonarchSender = null;
_lastPatronSender = null;
}
}
@ -77,17 +101,34 @@ public sealed class ChatCommandTargetState : IDisposable
private void OnEntryAppended(ChatEntry entry)
{
if (entry.Kind != ChatKind.Tell || string.IsNullOrEmpty(entry.Sender))
if (string.IsNullOrEmpty(entry.Sender))
return;
lock (_gate)
{
if (_disposed)
return;
if (entry.SenderGuid != 0u)
_lastIncomingTellSender = entry.Sender;
else
_lastOutgoingTellTarget = entry.Sender;
if (entry.Kind == ChatKind.Tell)
{
if (entry.SenderGuid != 0u)
_lastIncomingTellSender = entry.Sender;
else
_lastOutgoingTellTarget = entry.Sender;
return;
}
// gmCCommunicationSystem keeps independent reply targets for the
// legacy Monarch (0x4000) and Patron (0x2000) broadcasts. The
// legacy 0x0147 wire payload has no sender GUID, so its committed
// ChatEntry correctly carries zero even for an incoming speaker.
// Local channel echoes have an empty Sender and were rejected at
// the top of this method; the non-empty name is the discriminator.
if (entry.Kind != ChatKind.Channel)
return;
if (entry.ChannelId == 0x00004000u)
_lastMonarchSender = entry.Sender;
else if (entry.ChannelId == 0x00002000u)
_lastPatronSender = entry.Sender;
}
}
}

View file

@ -33,18 +33,19 @@ public static class InventoryFailureMessages
string itemName,
uint weenieError)
{
// ServerSaysAttemptFailed's verb switch. acdream has no latched kind
// for retail's IR_MOVE ("moved") or IR_WIELD ("wielded") today —
// wields ride AutoWieldController without the single-request gate —
// so those rows are absent rather than guessed onto a wrong kind.
// ServerSaysAttemptFailed's complete verb switch. The enum values are
// named by operation rather than retail's numeric IR_* values, but the
// wording and NAME_PLURAL/NAME_APPROPRIATE choice are verbatim.
string? verb = kind switch
{
InventoryRequestKind.Merge => "merged",
InventoryRequestKind.SplitToContainer => "split",
InventoryRequestKind.SplitToWorld => "split",
InventoryRequestKind.Move => "moved",
InventoryRequestKind.Pickup => "picked up",
InventoryRequestKind.PutInContainer => "put in the container",
InventoryRequestKind.DropToWorld => "dropped",
InventoryRequestKind.Wield => "wielded",
InventoryRequestKind.Give => "given",
_ => null,
};