feat(chat): port retail client command families

Expand the typed client-command boundary across travel, character queries, local UI and layout controls, AFK and consent, emotes, friends, squelch and filters, and fill-components. Preserve retail packet layouts and queue ownership, import the confirmation dialog, and keep authoritative social state in Core.

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-13 14:50:15 +02:00
parent 5a45a7ac7f
commit 9ea579bdd0
47 changed files with 6659 additions and 99 deletions

View file

@ -0,0 +1,74 @@
namespace AcDream.Core.Social;
/// <summary>Authoritative client-side copy of retail's <c>FriendDataList</c>.</summary>
public sealed class FriendsState
{
private readonly object _gate = new();
private readonly List<FriendEntry> _entries = new();
public IReadOnlyList<FriendEntry> Snapshot()
{
lock (_gate) return _entries.ToArray();
}
public void Apply(FriendsUpdate update)
{
ArgumentNullException.ThrowIfNull(update);
lock (_gate)
{
switch (update.Type)
{
case FriendsUpdateType.Full:
_entries.Clear();
_entries.AddRange(update.Entries);
break;
case FriendsUpdateType.Add:
foreach (FriendEntry entry in update.Entries)
{
int index = _entries.FindIndex(candidate => candidate.Id == entry.Id);
if (index >= 0) _entries[index] = entry;
else _entries.Add(entry);
}
break;
case FriendsUpdateType.Remove:
case FriendsUpdateType.RemoveSilent:
foreach (FriendEntry entry in update.Entries)
_entries.RemoveAll(candidate => candidate.Id == entry.Id);
break;
case FriendsUpdateType.OnlineStatus:
foreach (FriendEntry entry in update.Entries)
{
int index = _entries.FindIndex(candidate => candidate.Id == entry.Id);
if (index >= 0) _entries[index] = entry;
}
break;
}
}
}
public void Clear()
{
lock (_gate) _entries.Clear();
}
}
public sealed record FriendEntry(
uint Id,
string Name,
bool Online,
bool AppearOffline,
IReadOnlyList<uint> Friends,
IReadOnlyList<uint> FriendOf);
public enum FriendsUpdateType : uint
{
Full = 0,
Add = 1,
Remove = 2,
RemoveSilent = 3,
OnlineStatus = 4,
}
public sealed record FriendsUpdate(
FriendsUpdateType Type,
IReadOnlyList<FriendEntry> Entries);

View file

@ -0,0 +1,35 @@
namespace AcDream.Core.Social;
/// <summary>Authoritative copy of retail's server-supplied <c>SquelchDB</c>.</summary>
public sealed class SquelchState
{
private readonly object _gate = new();
private SquelchDatabase _database = SquelchDatabase.Empty;
public SquelchDatabase Snapshot()
{
lock (_gate) return _database;
}
public void Replace(SquelchDatabase database)
{
ArgumentNullException.ThrowIfNull(database);
lock (_gate) _database = database;
}
}
public sealed record SquelchInfo(
string Name,
bool AccountWide,
IReadOnlySet<uint> MessageTypes);
public sealed record SquelchDatabase(
IReadOnlyDictionary<string, uint> Accounts,
IReadOnlyDictionary<uint, SquelchInfo> Characters,
SquelchInfo Global)
{
public static SquelchDatabase Empty { get; } = new(
new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase),
new Dictionary<uint, SquelchInfo>(),
new SquelchInfo(string.Empty, false, new HashSet<uint>()));
}