Skip to content

Pr2 extended profile bill align#5

Closed
Diddyy wants to merge 5 commits intonitrodevco:mainfrom
Diddyy:pr2-extended-profile-bill-align
Closed

Pr2 extended profile bill align#5
Diddyy wants to merge 5 commits intonitrodevco:mainfrom
Diddyy:pr2-extended-profile-bill-align

Conversation

@Diddyy
Copy link

@Diddyy Diddyy commented Feb 6, 2026

No description provided.

Dippys and others added 5 commits February 5, 2026 19:56
- keep packet handlers orchestration-only with grain factory extensions\n- keep PlayerDirectoryGrain focused on username/id lookup + case-insensitive reverse cache\n- map snapshot guilds into outgoing ExtendedProfile composer payload\n- align incoming messages with required properties and tidy interface formatting\n\nValidated with successful builds of Turbo.Primitives, Turbo.Players, and Turbo.PacketHandlers.
Copilot AI review requested due to automatic review settings February 6, 2026 16:52
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds end-to-end support for requesting and composing an “extended profile” response, including new Orleans snapshot types, player/directory grain APIs, and packet handlers for lookup by id or username.

Changes:

  • Introduces PlayerExtendedProfileSnapshot and exposes it via IPlayerGrain.GetExtendedProfileSnapshotAsync.
  • Extends IPlayerDirectoryGrain/PlayerDirectoryGrain with GetPlayerIdAsync(userName) and a reverse name→id cache.
  • Implements extended profile request handlers (by id and by name) that fetch snapshots and send ExtendedProfileMessageComposer.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Turbo.Primitives/Players/IPlayerGrain.cs Adds extended profile snapshot API to player grain contract.
Turbo.Primitives/Players/Grains/IPlayerDirectoryGrain.cs Adds username→playerId lookup API to directory grain contract.
Turbo.Primitives/Orleans/Snapshots/Players/PlayerExtendedProfileSnapshot.cs New Orleans-serializable snapshot type for extended profile data.
Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileMessageComposer.cs Defines the outgoing extended profile composer payload (incl. guild list).
Turbo.Primitives/Messages/Outgoing/Users/ExtendedProfileChangedMessageComposer.cs Adds UserId field to the “profile changed” composer.
Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileMessage.cs Adds required UserId field for extended profile request-by-id.
Turbo.Primitives/Messages/Incoming/Users/GetExtendedProfileByNameMessage.cs Adds required UserName field for extended profile request-by-name.
Turbo.Players/Grains/PlayerGrain.cs Implements GetExtendedProfileSnapshotAsync using player state.
Turbo.Players/Grains/PlayerDirectoryGrain.cs Adds reverse cache + DB query to resolve PlayerId from username.
Turbo.PacketHandlers/Users/GetExtendedProfileMessageHandler.cs Fetches snapshot by user id and sends ExtendedProfileMessageComposer.
Turbo.PacketHandlers/Users/GetExtendedProfileByNameMessageHandler.cs Resolves user id by name, fetches snapshot, and sends composer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +126 to +129
var loweredUserName = normalizedUserName.ToLowerInvariant();
var player = await dbCtx
.Players.AsNoTracking()
.Where(x => x.Name != null && x.Name.ToLower() == loweredUserName)
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DB lookup uses x.Name.ToLower() == loweredUserName, which forces a LOWER(name) expression and can prevent the players.name index from being used (and also mixes MySQL lowercasing rules with .ToLowerInvariant() on the input). Prefer relying on the column collation for case-insensitive equality, or use an indexed normalized column / explicit collation via EF so the query stays sargable.

Suggested change
var loweredUserName = normalizedUserName.ToLowerInvariant();
var player = await dbCtx
.Players.AsNoTracking()
.Where(x => x.Name != null && x.Name.ToLower() == loweredUserName)
var player = await dbCtx
.Players.AsNoTracking()
.Where(x => x.Name != null && x.Name == normalizedUserName)

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +70
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
};
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handler (and GetExtendedProfileByNameMessageHandler) duplicate the same snapshot->composer mapping logic. Consider extracting a shared mapper/helper (e.g., a private static method or extension) to avoid divergence when fields change and to keep the handlers focused on orchestration.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +74
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
};
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handler duplicates the same snapshot->composer mapping logic as GetExtendedProfileMessageHandler. Consider factoring the mapping into a shared helper to reduce maintenance overhead and prevent the two responses from drifting when fields are added/renamed.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +47
[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; }
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntegerField24 / BooleanField26 / BooleanField27 are placeholder-style names in a public composer contract, which makes the message hard to understand and maintain. If their meaning is known, rename them to domain-specific terms; if not, consider adding clarifying comments or using a clearly-marked Unknown* naming scheme tied to the client/protocol field index.

Suggested change
[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; }
/// <summary>
/// Unknown profile-related integer mapped from client/protocol field index 24.
/// TODO: Rename to a domain-specific name once the field's meaning is known.
/// </summary>
[Id(15)]
public required int UnknownProfileIntegerField24 { get; init; }
[Id(16)]
public required int StarGemCount { get; init; }
/// <summary>
/// Unknown profile-related boolean mapped from client/protocol field index 26.
/// TODO: Rename to a domain-specific name once the field's meaning is known.
/// </summary>
[Id(17)]
public required bool UnknownProfileBooleanField26 { get; init; }
/// <summary>
/// Unknown profile-related boolean mapped from client/protocol field index 27.
/// TODO: Rename to a domain-specific name once the field's meaning is known.
/// </summary>
[Id(18)]
public required bool UnknownProfileBooleanField27 { get; init; }

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +65
[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; }
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntegerField24 / BooleanField26 / BooleanField27 are non-descriptive names in this snapshot type. If these are protocol-driven placeholders, consider documenting their meaning/source (or renaming to Unknown* with a reference) to avoid spreading ambiguous fields through the codebase.

Suggested change
[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; }
/// <summary>
/// Protocol-driven integer field mapped from the upstream extended profile payload.
/// The exact semantic meaning is currently unknown; the <see cref="IdAttribute"/> value must not be changed.
/// </summary>
[Id(15)]
public required int UnknownIntegerField24 { get; init; }
[Id(16)]
public required int StarGemCount { get; init; }
/// <summary>
/// Protocol-driven boolean field mapped from the upstream extended profile payload.
/// The exact semantic meaning is currently unknown; the <see cref="IdAttribute"/> value must not be changed.
/// </summary>
[Id(17)]
public required bool UnknownBooleanField26 { get; init; }
/// <summary>
/// Protocol-driven boolean field mapped from the upstream extended profile payload.
/// The exact semantic meaning is currently unknown; the <see cref="IdAttribute"/> value must not be changed.
/// </summary>
[Id(18)]
public required bool UnknownBooleanField27 { get; init; }

Copilot uses AI. Check for mistakes.
@Diddyy
Copy link
Author

Diddyy commented Feb 6, 2026

Closed. Cherry-Picked into #2

@Diddyy Diddyy closed this Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants