diff --git a/Turbo.PacketHandlers/Users/GetExtendedProfileByNameMessageHandler.cs b/Turbo.PacketHandlers/Users/GetExtendedProfileByNameMessageHandler.cs index faa0ba41..1f370070 100644 --- a/Turbo.PacketHandlers/Users/GetExtendedProfileByNameMessageHandler.cs +++ b/Turbo.PacketHandlers/Users/GetExtendedProfileByNameMessageHandler.cs @@ -1,19 +1,78 @@ +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Orleans; using Turbo.Messages.Registry; using Turbo.Primitives.Messages.Incoming.Users; +using Turbo.Primitives.Messages.Outgoing.Users; +using Turbo.Primitives.Orleans; +using Turbo.Primitives.Players; namespace Turbo.PacketHandlers.Users; public class GetExtendedProfileByNameMessageHandler : IMessageHandler { + private readonly IGrainFactory _grainFactory; + + public GetExtendedProfileByNameMessageHandler(IGrainFactory grainFactory) + { + _grainFactory = grainFactory; + } + public async ValueTask HandleAsync( GetExtendedProfileByNameMessage message, MessageContext ctx, CancellationToken ct ) { - await ValueTask.CompletedTask.ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(message.UserName)) + return; + + var directoryGrain = _grainFactory.GetPlayerDirectoryGrain(); + var playerId = await directoryGrain.GetPlayerIdAsync(message.UserName, ct).ConfigureAwait(false); + if (playerId is null) + return; + var snapshot = await _grainFactory + .GetPlayerGrain(playerId.Value) + .GetExtendedProfileSnapshotAsync(ct) + .ConfigureAwait(false); + + var response = new ExtendedProfileMessageComposer + { + UserId = (int)snapshot.UserId, + UserName = snapshot.UserName, + Figure = snapshot.Figure, + Motto = snapshot.Motto, + CreationDate = snapshot.CreationDate, + AchievementScore = snapshot.AchievementScore, + FriendCount = snapshot.FriendCount, + IsFriend = snapshot.IsFriend, + IsFriendRequestSent = snapshot.IsFriendRequestSent, + IsOnline = snapshot.IsOnline, + Guilds = snapshot + .Guilds.Select(x => new GuildInfo + { + GroupId = x.GroupId, + GroupName = x.GroupName, + BadgeCode = x.BadgeCode, + PrimaryColor = x.PrimaryColor, + SecondaryColor = x.SecondaryColor, + Favourite = x.Favourite, + OwnerId = x.OwnerId, + HasForum = x.HasForum + }) + .ToList(), + LastAccessSinceInSeconds = snapshot.LastAccessSinceInSeconds, + OpenProfileWindow = snapshot.OpenProfileWindow, + IsHidden = snapshot.IsHidden, + AccountLevel = snapshot.AccountLevel, + IntegerField24 = snapshot.IntegerField24, + StarGemCount = snapshot.StarGemCount, + BooleanField26 = snapshot.BooleanField26, + BooleanField27 = snapshot.BooleanField27 + }; + + await ctx.SendComposerAsync(response, ct).ConfigureAwait(false); } } diff --git a/Turbo.PacketHandlers/Users/GetExtendedProfileMessageHandler.cs b/Turbo.PacketHandlers/Users/GetExtendedProfileMessageHandler.cs index 4567bdcf..9b92cf00 100644 --- a/Turbo.PacketHandlers/Users/GetExtendedProfileMessageHandler.cs +++ b/Turbo.PacketHandlers/Users/GetExtendedProfileMessageHandler.cs @@ -1,18 +1,74 @@ +using System.Linq; using System.Threading; using System.Threading.Tasks; +using Orleans; using Turbo.Messages.Registry; using Turbo.Primitives.Messages.Incoming.Users; +using Turbo.Primitives.Messages.Outgoing.Users; +using Turbo.Primitives.Orleans; +using Turbo.Primitives.Players; namespace Turbo.PacketHandlers.Users; public class GetExtendedProfileMessageHandler : IMessageHandler { + private readonly IGrainFactory _grainFactory; + + public GetExtendedProfileMessageHandler(IGrainFactory grainFactory) + { + _grainFactory = grainFactory; + } + public async ValueTask HandleAsync( GetExtendedProfileMessage message, MessageContext ctx, CancellationToken ct ) { - await ValueTask.CompletedTask.ConfigureAwait(false); + var targetUserId = message.UserId; + if (targetUserId <= 0) + return; + + var snapshot = await _grainFactory + .GetPlayerGrain(targetUserId) + .GetExtendedProfileSnapshotAsync(ct) + .ConfigureAwait(false); + + var response = new ExtendedProfileMessageComposer + { + UserId = (int)snapshot.UserId, + UserName = snapshot.UserName, + Figure = snapshot.Figure, + Motto = snapshot.Motto, + CreationDate = snapshot.CreationDate, + AchievementScore = snapshot.AchievementScore, + FriendCount = snapshot.FriendCount, + IsFriend = snapshot.IsFriend, + IsFriendRequestSent = snapshot.IsFriendRequestSent, + IsOnline = snapshot.IsOnline, + Guilds = snapshot + .Guilds.Select(x => new GuildInfo + { + GroupId = x.GroupId, + GroupName = x.GroupName, + BadgeCode = x.BadgeCode, + PrimaryColor = x.PrimaryColor, + SecondaryColor = x.SecondaryColor, + Favourite = x.Favourite, + OwnerId = x.OwnerId, + HasForum = x.HasForum + }) + .ToList(), + LastAccessSinceInSeconds = snapshot.LastAccessSinceInSeconds, + OpenProfileWindow = snapshot.OpenProfileWindow, + IsHidden = snapshot.IsHidden, + AccountLevel = snapshot.AccountLevel, + IntegerField24 = snapshot.IntegerField24, + StarGemCount = snapshot.StarGemCount, + BooleanField26 = snapshot.BooleanField26, + BooleanField27 = snapshot.BooleanField27 + }; + + await ctx.SendComposerAsync(response, ct).ConfigureAwait(false); } } diff --git a/Turbo.Players/Grains/PlayerDirectoryGrain.cs b/Turbo.Players/Grains/PlayerDirectoryGrain.cs index 99d6df66..20067c5a 100644 --- a/Turbo.Players/Grains/PlayerDirectoryGrain.cs +++ b/Turbo.Players/Grains/PlayerDirectoryGrain.cs @@ -19,6 +19,9 @@ internal class PlayerDirectoryGrain(IDbContextFactory dbCtxFacto private readonly IDbContextFactory _dbCtxFactory = dbCtxFactory; private readonly Dictionary _idToName = []; + private readonly Dictionary _nameToId = new( + System.StringComparer.OrdinalIgnoreCase + ); public async Task GetPlayerNameAsync(PlayerId playerId, CancellationToken ct) { @@ -36,7 +39,7 @@ public async Task GetPlayerNameAsync(PlayerId playerId, CancellationToke if (string.IsNullOrWhiteSpace(dbName)) return string.Empty; - _idToName[playerId] = dbName; + SetCache(playerId, dbName); return dbName; } @@ -84,7 +87,7 @@ CancellationToken ct foreach (var player in players) { - _idToName[player.Key] = player.Value; + SetCache(player.Key, player.Value); names.TryAdd(player.Key, player.Value); } @@ -96,15 +99,52 @@ CancellationToken ct public Task SetPlayerNameAsync(PlayerId playerId, string name, CancellationToken ct) { - _idToName[playerId] = name; + SetCache(playerId, name); return Task.CompletedTask; } public Task InvalidatePlayerNameAsync(PlayerId playerId, CancellationToken ct) { - _idToName.Remove(playerId); + if (_idToName.Remove(playerId, out var oldName)) + _nameToId.Remove(oldName); return Task.CompletedTask; } + + public async Task GetPlayerIdAsync(string userName, CancellationToken ct) + { + if (string.IsNullOrWhiteSpace(userName)) + return null; + + var normalizedUserName = userName.Trim(); + if (_nameToId.TryGetValue(normalizedUserName, out var cachedPlayerId)) + return cachedPlayerId; + + await using var dbCtx = await _dbCtxFactory.CreateDbContextAsync(ct); + + var loweredUserName = normalizedUserName.ToLowerInvariant(); + var player = await dbCtx + .Players.AsNoTracking() + .Where(x => x.Name != null && x.Name.ToLower() == loweredUserName) + .Select(x => new { x.Id, x.Name }) + .FirstOrDefaultAsync(ct); + + if (player is null or { Id: <= 0 }) + return null; + + var playerId = (PlayerId)player.Id; + SetCache(playerId, player.Name ?? normalizedUserName); + + return playerId; + } + + private void SetCache(PlayerId playerId, string name) + { + if (_idToName.TryGetValue(playerId, out var existingName)) + _nameToId.Remove(existingName); + + _idToName[playerId] = name; + _nameToId[name] = playerId; + } } diff --git a/Turbo.Players/Grains/PlayerGrain.cs b/Turbo.Players/Grains/PlayerGrain.cs index 8b5379a5..34817f19 100644 --- a/Turbo.Players/Grains/PlayerGrain.cs +++ b/Turbo.Players/Grains/PlayerGrain.cs @@ -93,4 +93,33 @@ public Task GetSummaryAsync(CancellationToken ct) => CreatedAt = state.State.CreatedAt, } ); + + public Task GetExtendedProfileSnapshotAsync(CancellationToken ct) + { + var s = state.State; + return Task.FromResult( + new PlayerExtendedProfileSnapshot + { + UserId = (PlayerId)this.GetPrimaryKeyLong(), + UserName = s.Name, + Figure = s.Figure, + Motto = s.Motto, + CreationDate = s.CreatedAt.ToString("yyyy-MM-dd"), + AchievementScore = 0, + FriendCount = 0, + IsFriend = false, + IsFriendRequestSent = false, + IsOnline = true, + Guilds = new(), + LastAccessSinceInSeconds = 0, + OpenProfileWindow = true, + IsHidden = false, + AccountLevel = 1, + IntegerField24 = 0, + StarGemCount = 0, + BooleanField26 = false, + BooleanField27 = false + } + ); + } } diff --git a/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileByNameMessage.cs b/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileByNameMessage.cs index dba00f00..06180ae3 100644 --- a/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileByNameMessage.cs +++ b/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileByNameMessage.cs @@ -2,4 +2,7 @@ namespace Turbo.Primitives.Messages.Incoming.Users; -public record GetExtendedProfileByNameMessage : IMessageEvent { } +public record GetExtendedProfileByNameMessage : IMessageEvent +{ + public required string UserName { get; init; } +} diff --git a/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileMessage.cs b/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileMessage.cs index e3dad237..96140d2e 100644 --- a/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileMessage.cs +++ b/Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileMessage.cs @@ -1,5 +1,9 @@ using Turbo.Primitives.Networking; +using Turbo.Primitives.Players; namespace Turbo.Primitives.Messages.Incoming.Users; -public record GetExtendedProfileMessage : IMessageEvent { } +public record GetExtendedProfileMessage : IMessageEvent +{ + public required PlayerId UserId { get; init; } +} diff --git a/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileChangedMessageComposer.cs b/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileChangedMessageComposer.cs index ae71448e..203b1092 100644 --- a/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileChangedMessageComposer.cs +++ b/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileChangedMessageComposer.cs @@ -6,5 +6,6 @@ namespace Turbo.Primitives.Messages.Outgoing.Users; [GenerateSerializer, Immutable] public sealed record ExtendedProfileChangedMessageComposer : IComposer { - // TODO: add properties if/when identified + [Id(0)] + public required int UserId { get; init; } } diff --git a/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileMessageComposer.cs b/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileMessageComposer.cs index d5085374..0a9f9ae1 100644 --- a/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileMessageComposer.cs +++ b/Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileMessageComposer.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Orleans; using Turbo.Primitives.Networking; @@ -6,5 +7,63 @@ namespace Turbo.Primitives.Messages.Outgoing.Users; [GenerateSerializer, Immutable] public sealed record ExtendedProfileMessageComposer : IComposer { - // TODO: add properties if/when identified + [Id(0)] + public required int UserId { get; init; } + [Id(1)] + public required string UserName { get; init; } + [Id(2)] + public required string Figure { get; init; } + [Id(3)] + public required string Motto { get; init; } + [Id(4)] + public required string CreationDate { get; init; } + [Id(5)] + public required int AchievementScore { get; init; } + [Id(6)] + public required int FriendCount { get; init; } + [Id(7)] + public required bool IsFriend { get; init; } + [Id(8)] + public required bool IsFriendRequestSent { get; init; } + [Id(9)] + public required bool IsOnline { get; init; } + [Id(10)] + public required List Guilds { get; init; } + [Id(11)] + public required int LastAccessSinceInSeconds { get; init; } + [Id(12)] + public required bool OpenProfileWindow { get; init; } + [Id(13)] + public required bool IsHidden { get; init; } + [Id(14)] + public required int AccountLevel { get; init; } + [Id(15)] + public required int IntegerField24 { get; init; } + [Id(16)] + public required int StarGemCount { get; init; } + [Id(17)] + public required bool BooleanField26 { get; init; } + [Id(18)] + public required bool BooleanField27 { get; init; } +} + +[GenerateSerializer, Immutable] +public sealed record GuildInfo +{ + [Id(0)] + public required int GroupId { get; init; } + [Id(1)] + public required string GroupName { get; init; } + [Id(2)] + public required string BadgeCode { get; init; } + [Id(3)] + public required string PrimaryColor { get; init; } + [Id(4)] + public required string SecondaryColor { get; init; } + [Id(5)] + public required bool Favourite { get; init; } + [Id(6)] + public required int OwnerId { get; init; } + [Id(7)] + public required bool HasForum { get; init; } } diff --git a/Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs b/Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs new file mode 100644 index 00000000..c90bf26b --- /dev/null +++ b/Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using Orleans; +using Turbo.Primitives.Players; + +namespace Turbo.Primitives.Orleans.Snapshots.Players; + +[GenerateSerializer, Immutable] +public sealed record PlayerExtendedProfileSnapshot +{ + [Id(0)] + public required PlayerId UserId { get; init; } + + [Id(1)] + public required string UserName { get; init; } + + [Id(2)] + public required string Figure { get; init; } + + [Id(3)] + public required string Motto { get; init; } + + [Id(4)] + public required string CreationDate { get; init; } + + [Id(5)] + public required int AchievementScore { get; init; } + + [Id(6)] + public required int FriendCount { get; init; } + + [Id(7)] + public required bool IsFriend { get; init; } + + [Id(8)] + public required bool IsFriendRequestSent { get; init; } + + [Id(9)] + public required bool IsOnline { get; init; } + + [Id(10)] + public required List Guilds { get; init; } + + [Id(11)] + public required int LastAccessSinceInSeconds { get; init; } + + [Id(12)] + public required bool OpenProfileWindow { get; init; } + + [Id(13)] + public required bool IsHidden { get; init; } + + [Id(14)] + public required int AccountLevel { get; init; } + + [Id(15)] + public required int IntegerField24 { get; init; } + + [Id(16)] + public required int StarGemCount { get; init; } + + [Id(17)] + public required bool BooleanField26 { get; init; } + + [Id(18)] + public required bool BooleanField27 { get; init; } + + [GenerateSerializer, Immutable] + public sealed record GuildInfo + { + [Id(0)] + public required int GroupId { get; init; } + + [Id(1)] + public required string GroupName { get; init; } + + [Id(2)] + public required string BadgeCode { get; init; } + + [Id(3)] + public required string PrimaryColor { get; init; } + + [Id(4)] + public required string SecondaryColor { get; init; } + + [Id(5)] + public required bool Favourite { get; init; } + + [Id(6)] + public required int OwnerId { get; init; } + + [Id(7)] + public required bool HasForum { get; init; } + } +} diff --git a/Turbo.Primitives/Players/Grains/IPlayerDirectoryGrain.cs b/Turbo.Primitives/Players/Grains/IPlayerDirectoryGrain.cs index 5916058e..bd697df6 100644 --- a/Turbo.Primitives/Players/Grains/IPlayerDirectoryGrain.cs +++ b/Turbo.Primitives/Players/Grains/IPlayerDirectoryGrain.cs @@ -13,6 +13,7 @@ public Task> GetPlayerNamesAsync( List playerIds, CancellationToken ct ); + public Task GetPlayerIdAsync(string userName, CancellationToken ct); public Task SetPlayerNameAsync(PlayerId playerId, string name, CancellationToken ct); public Task InvalidatePlayerNameAsync(PlayerId playerId, CancellationToken ct); } diff --git a/Turbo.Primitives/Players/IPlayerGrain.cs b/Turbo.Primitives/Players/IPlayerGrain.cs index 918f7743..dbc16712 100644 --- a/Turbo.Primitives/Players/IPlayerGrain.cs +++ b/Turbo.Primitives/Players/IPlayerGrain.cs @@ -8,4 +8,5 @@ namespace Turbo.Primitives.Players; public interface IPlayerGrain : IGrainWithIntegerKey { public Task GetSummaryAsync(CancellationToken ct); + public Task GetExtendedProfileSnapshotAsync(CancellationToken ct); }