diff --git a/WheelWizard.Test/Features/WhWzDataTests.cs b/WheelWizard.Test/Features/WhWzDataTests.cs index 13a586dd..f1f9366b 100644 --- a/WheelWizard.Test/Features/WhWzDataTests.cs +++ b/WheelWizard.Test/Features/WhWzDataTests.cs @@ -160,7 +160,10 @@ public async Task LoadBadgesAsync_OverwritesExistingBadges_WhenCalledMultipleTim Assert.Contains(BadgeVariant.WhWzDev, initialBadges); // Arrange - Second load with different data - var updatedBadgeData = new Dictionary { { "FC1", [BadgeVariant.Translator, BadgeVariant.Firestarter_GoldWinner] } }; + var updatedBadgeData = new Dictionary + { + { "FC1", [BadgeVariant.Translator, BadgeVariant.Firestarter_GoldWinner] }, + }; _apiCaller .CallApiAsync(Arg.Any>>>>()) diff --git a/WheelWizard/Features/RrRooms/Domain/IRwfcApi.cs b/WheelWizard/Features/RrRooms/Domain/IRwfcApi.cs index f3603560..8a5d7e78 100644 --- a/WheelWizard/Features/RrRooms/Domain/IRwfcApi.cs +++ b/WheelWizard/Features/RrRooms/Domain/IRwfcApi.cs @@ -4,6 +4,9 @@ namespace WheelWizard.RrRooms; public interface IRwfcApi { - [Get("/api/groups")] - Task> GetWiiGroupsAsync(); + [Get("/api/roomstatus")] + Task GetRoomStatusAsync(); + + [Get("/api/leaderboard/top/{limit}")] + Task> GetTopLeaderboardAsync(int limit); } diff --git a/WheelWizard/Features/RrRooms/Domain/RwfcLeaderboardEntry.cs b/WheelWizard/Features/RrRooms/Domain/RwfcLeaderboardEntry.cs new file mode 100644 index 00000000..c72a4fcf --- /dev/null +++ b/WheelWizard/Features/RrRooms/Domain/RwfcLeaderboardEntry.cs @@ -0,0 +1,20 @@ +namespace WheelWizard.RrRooms; + +public sealed class RwfcLeaderboardEntry +{ + public required string Pid { get; set; } + public string Name { get; set; } = string.Empty; + public string FriendCode { get; set; } = string.Empty; + + public int? Vr { get; set; } + public int? Rank { get; set; } + public int? ActiveRank { get; set; } + + public DateTime? LastSeen { get; set; } + public bool IsActive { get; set; } + public bool IsSuspicious { get; set; } + + public RwfcLeaderboardVrStats? VrStats { get; set; } + + public string? MiiImageBase64 { get; set; } +} diff --git a/WheelWizard/Features/RrRooms/Domain/RwfcLeaderboardVrStats.cs b/WheelWizard/Features/RrRooms/Domain/RwfcLeaderboardVrStats.cs new file mode 100644 index 00000000..1b204d73 --- /dev/null +++ b/WheelWizard/Features/RrRooms/Domain/RwfcLeaderboardVrStats.cs @@ -0,0 +1,8 @@ +namespace WheelWizard.RrRooms; + +public sealed class RwfcLeaderboardVrStats +{ + public int Last24Hours { get; set; } = 0; + public int LastWeek { get; set; } = 0; + public int LastMonth { get; set; } = 0; +} diff --git a/WheelWizard/Features/RrRooms/Domain/RwfcPlayer.cs b/WheelWizard/Features/RrRooms/Domain/RwfcPlayer.cs deleted file mode 100644 index 893ad546..00000000 --- a/WheelWizard/Features/RrRooms/Domain/RwfcPlayer.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace WheelWizard.RrRooms; - -public sealed class RwfcPlayer -{ - public required string Count { get; set; } - public required string Pid { get; set; } - public required string Name { get; set; } - public required string ConnMap { get; set; } - public required string ConnFail { get; set; } - public required string Suspend { get; set; } - public required string Fc { get; set; } - public string Ev { get; set; } = "--"; - public string Eb { get; set; } = "--"; - public List Mii { get; set; } = []; -} diff --git a/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusPlayer.cs b/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusPlayer.cs new file mode 100644 index 00000000..568a807d --- /dev/null +++ b/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusPlayer.cs @@ -0,0 +1,19 @@ +namespace WheelWizard.RrRooms; + +public sealed class RwfcRoomStatusPlayer +{ + public required string Pid { get; set; } + public string Name { get; set; } = string.Empty; + + public string FriendCode { get; set; } = string.Empty; + + public int? Vr { get; set; } + public int? Br { get; set; } + + public bool IsOpenHost { get; set; } + public bool IsSuspended { get; set; } + + public RwfcMii? Mii { get; set; } + + public List ConnectionMap { get; set; } = []; +} diff --git a/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusResponse.cs b/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusResponse.cs new file mode 100644 index 00000000..88c346b7 --- /dev/null +++ b/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusResponse.cs @@ -0,0 +1,13 @@ +namespace WheelWizard.RrRooms; + +public sealed class RwfcRoomStatusResponse +{ + public required List Rooms { get; set; } + + //todo: figure out what these refer to: + public DateTime Timestamp { get; set; } + + public int? Id { get; set; } + public int? MinimumId { get; set; } + public int? MaximumId { get; set; } +} diff --git a/WheelWizard/Features/RrRooms/Domain/RwfcRoom.cs b/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusRoom.cs similarity index 57% rename from WheelWizard/Features/RrRooms/Domain/RwfcRoom.cs rename to WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusRoom.cs index 9aa3ed5f..153501de 100644 --- a/WheelWizard/Features/RrRooms/Domain/RwfcRoom.cs +++ b/WheelWizard/Features/RrRooms/Domain/RwfcRoomStatusRoom.cs @@ -1,14 +1,16 @@ namespace WheelWizard.RrRooms; -public sealed class RwfcRoom +public sealed class RwfcRoomStatusRoom { public required string Id { get; set; } - public string? Game { get; set; } - public required DateTime Created { get; set; } public required string Type { get; set; } - public required bool Suspend { get; set; } + public required DateTime Created { get; set; } + public string? Host { get; set; } public string? Rk { get; set; } - public required Dictionary Players { get; set; } + + public required List Players { get; set; } + + public bool Suspend { get; set; } } diff --git a/WheelWizard/Features/RrRooms/RrLeaderboardSingletonService.cs b/WheelWizard/Features/RrRooms/RrLeaderboardSingletonService.cs new file mode 100644 index 00000000..bedddc6c --- /dev/null +++ b/WheelWizard/Features/RrRooms/RrLeaderboardSingletonService.cs @@ -0,0 +1,17 @@ +using WheelWizard.Shared.Services; + +namespace WheelWizard.RrRooms; + +public interface IRrLeaderboardSingletonService +{ + Task>> GetTopPlayersAsync(int limit = 50); +} + +public class RrLeaderboardSingletonService(IApiCaller apiCaller) : IRrLeaderboardSingletonService +{ + public async Task>> GetTopPlayersAsync(int limit = 50) + { + var boundedLimit = Math.Clamp(limit, 1, 200); + return await apiCaller.CallApiAsync(api => api.GetTopLeaderboardAsync(boundedLimit)); + } +} diff --git a/WheelWizard/Features/RrRooms/RrRoomsExtensions.cs b/WheelWizard/Features/RrRooms/RrRoomsExtensions.cs index 979550db..f326a6ba 100644 --- a/WheelWizard/Features/RrRooms/RrRoomsExtensions.cs +++ b/WheelWizard/Features/RrRooms/RrRoomsExtensions.cs @@ -7,9 +7,13 @@ public static class RrRoomsExtensions { public static IServiceCollection AddRrRooms(this IServiceCollection services) { - services.AddWhWzRefitApi(Endpoints.RwfcBaseAddress, new() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }); + services.AddWhWzRefitApi( + Endpoints.RwfcBaseAddress, + new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase } + ); services.AddSingleton(); + services.AddSingleton(); return services; } diff --git a/WheelWizard/Features/RrRooms/RrRoomsSingletonService.cs b/WheelWizard/Features/RrRooms/RrRoomsSingletonService.cs index 35ad49e6..8be669ca 100644 --- a/WheelWizard/Features/RrRooms/RrRoomsSingletonService.cs +++ b/WheelWizard/Features/RrRooms/RrRoomsSingletonService.cs @@ -4,13 +4,17 @@ namespace WheelWizard.RrRooms; public interface IRrRoomsSingletonService { - Task>> GetRoomsAsync(); + Task>> GetRoomsAsync(); } public class RrRoomsSingletonService(IApiCaller apiCaller) : IRrRoomsSingletonService { - public async Task>> GetRoomsAsync() + public async Task>> GetRoomsAsync() { - return await apiCaller.CallApiAsync(rwfcApi => rwfcApi.GetWiiGroupsAsync()); + var result = await apiCaller.CallApiAsync(rwfcApi => rwfcApi.GetRoomStatusAsync()); + if (result.IsFailure) + return result.Error; + + return result.Value.Rooms; } } diff --git a/WheelWizard/Features/WiiManagement/GameLicense/Domain/PlayerProfileBase.cs b/WheelWizard/Features/WiiManagement/GameLicense/Domain/PlayerProfileBase.cs index ed8bad87..888ee2dc 100644 --- a/WheelWizard/Features/WiiManagement/GameLicense/Domain/PlayerProfileBase.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/Domain/PlayerProfileBase.cs @@ -24,8 +24,8 @@ public bool IsOnline if (currentRooms.Count <= 0) return false; - var onlinePlayers = currentRooms.SelectMany(room => room.Players.Values).ToList(); - return onlinePlayers.Any(player => player.Fc == FriendCode); + var onlinePlayers = currentRooms.SelectMany(room => room.Players).ToList(); + return onlinePlayers.Any(player => player.FriendCode == FriendCode); } set { diff --git a/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs b/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs index fc657914..aaccff05 100644 --- a/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs +++ b/WheelWizard/Features/WiiManagement/GameLicense/GameLicenseService.cs @@ -116,10 +116,10 @@ public GameLicenseSingletonService(IMiiDbService miiService, IFileSystem fileSys public void RefreshOnlineStatus() { var currentRooms = RRLiveRooms.Instance.CurrentRooms; - var onlinePlayers = currentRooms.SelectMany(room => room.Players.Values).ToList(); + var onlinePlayers = currentRooms.SelectMany(room => room.Players).ToList(); foreach (var user in Licenses.Users) { - user.IsOnline = onlinePlayers.Any(player => player.Fc == user.FriendCode); + user.IsOnline = onlinePlayers.Any(player => player.FriendCode == user.FriendCode); } } diff --git a/WheelWizard/Models/RRInfo/RrPlayer.cs b/WheelWizard/Models/RRInfo/RrPlayer.cs index a3a69feb..cadd5c18 100644 --- a/WheelWizard/Models/RRInfo/RrPlayer.cs +++ b/WheelWizard/Models/RRInfo/RrPlayer.cs @@ -3,25 +3,43 @@ namespace WheelWizard.Models.RRInfo; -public class RrPlayer +public class RrPlayer : IEquatable { - // These variables should not be renamed since they are directly mapped to the JSON object - public required string Count { get; set; } // you can have one Wii that with 2 players (and hence the Mii list) public required string Pid { get; set; } public required string Name { get; set; } - public required string ConnMap { get; set; } // its always there, but we dont use - public required string ConnFail { get; set; } - public required string Suspend { get; set; } - public required string Fc { get; set; } - public string Ev { get; set; } = "--"; // private games don't have EV and EB - public string Eb { get; set; } = "--"; - public List Mii { get; set; } = []; + public required string FriendCode { get; set; } - public int PlayerCount => int.Parse(Count); - public Mii? FirstMii => Mii.Count <= 0 ? null : Mii[0]; + public int? Vr { get; set; } + public int? Br { get; set; } - public int Vr => int.TryParse(Ev, out var evValue) ? evValue : -1; + public bool IsOpenHost { get; set; } + public bool IsSuspended { get; set; } + + public int? LeaderboardRank { get; set; } + public bool IsTopLeaderboardPlayer => LeaderboardRank.HasValue; + public string TopLabel => LeaderboardRank is { } rank ? $"#{rank}" : string.Empty; + + public List ConnectionMap { get; set; } = []; + + public Mii? Mii { get; set; } + + public Mii? FirstMii => Mii; + + public string VrDisplay => Vr?.ToString() ?? "--"; + public string BrDisplay => Br?.ToString() ?? "--"; public BadgeVariant[] BadgeVariants { get; set; } = []; public bool HasBadges => BadgeVariants.Length != 0; + + public bool Equals(RrPlayer? other) + { + if (other is null) + return false; + return string.Equals(Pid, other.Pid, StringComparison.Ordinal) + && string.Equals(FriendCode, other.FriendCode, StringComparison.Ordinal); + } + + public override bool Equals(object? obj) => obj is RrPlayer other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Pid, FriendCode); } diff --git a/WheelWizard/Models/RRInfo/RrRoom.cs b/WheelWizard/Models/RRInfo/RrRoom.cs index c598ddc5..394d5e6b 100644 --- a/WheelWizard/Models/RRInfo/RrRoom.cs +++ b/WheelWizard/Models/RRInfo/RrRoom.cs @@ -6,15 +6,13 @@ namespace WheelWizard.Models.RRInfo; public class RrRoom { public required string Id { get; set; } - public string? Game { get; set; } // it always exists, but we dont care since we dont use it (and its always "mariokartwii") public required DateTime Created { get; set; } public required string Type { get; set; } public required bool Suspend { get; set; } - public string? Host { get; set; } // the key of player in the players map (that started the room) public string? Rk { get; set; } // RK does not exists in private rooms - public required Dictionary Players { get; set; } + public required List Players { get; set; } - public int PlayerCount => Players.Sum(p => p.Value.PlayerCount); + public int PlayerCount => Players.Count; public string TimeOnline => Humanizer.HumanizeTimeSpan(DateTime.UtcNow - Created); public bool IsPublic => Type != "private"; @@ -139,7 +137,16 @@ public class RrRoom _ => IsPublic ? "Unknown Mode" : "Private Room", }; - public int AverageVr => PlayerCount == 0 ? 0 : Players.Sum(p => p.Value.Vr) / PlayerCount; + public int AverageVr + { + get + { + var vrs = Players.Select(p => p.Vr).Where(v => v.HasValue).Select(v => v!.Value).ToList(); + return vrs.Count == 0 ? 0 : (int)vrs.Average(); + } + } + + public RrPlayer? HostPlayer => Players.FirstOrDefault(p => p.IsOpenHost); - public Mii? HostMii => !string.IsNullOrEmpty(Host) ? Players.GetValueOrDefault(Host)?.FirstMii : null; + public Mii? HostMii => HostPlayer?.FirstMii; } diff --git a/WheelWizard/Services/Endpoints.cs b/WheelWizard/Services/Endpoints.cs index 7f7c8a8e..30475d47 100644 --- a/WheelWizard/Services/Endpoints.cs +++ b/WheelWizard/Services/Endpoints.cs @@ -5,7 +5,7 @@ public static class Endpoints /// /// The base address for accessing room data /// - public const string RwfcBaseAddress = "http://rwfc.net"; + public const string RwfcBaseAddress = "https://rwfc.net"; /// /// The base address for accessing the WheelWizard data (data that we control) diff --git a/WheelWizard/Services/LiveData/RRLiveRooms.cs b/WheelWizard/Services/LiveData/RRLiveRooms.cs index 0d19b9bf..99aef535 100644 --- a/WheelWizard/Services/LiveData/RRLiveRooms.cs +++ b/WheelWizard/Services/LiveData/RRLiveRooms.cs @@ -25,89 +25,174 @@ protected override async Task ExecuteTaskAsync() { var whWzService = App.Services.GetRequiredService(); var roomsService = App.Services.GetRequiredService(); + var leaderboardService = App.Services.GetRequiredService(); - var roomsResult = await roomsService.GetRoomsAsync(); + var roomsTask = roomsService.GetRoomsAsync(); + var leaderboardTask = leaderboardService.GetTopPlayersAsync(50); + + await Task.WhenAll(roomsTask, leaderboardTask); + + var roomsResult = roomsTask.Result; + var leaderboardResult = leaderboardTask.Result; if (roomsResult.IsFailure) { CurrentRooms = []; return; } + var leaderboardByPid = leaderboardResult.IsSuccess + ? leaderboardResult + .Value.Where(entry => !string.IsNullOrWhiteSpace(entry.Pid)) + .GroupBy(entry => entry.Pid, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.First(), StringComparer.Ordinal) + : new Dictionary(StringComparer.Ordinal); + + var leaderboardByFriendCode = leaderboardResult.IsSuccess + ? leaderboardResult + .Value.Where(entry => !string.IsNullOrWhiteSpace(entry.FriendCode)) + .GroupBy(entry => entry.FriendCode, StringComparer.Ordinal) + .ToDictionary(group => group.Key, group => group.First(), StringComparer.Ordinal) + : new Dictionary(StringComparer.Ordinal); + //source: https://kevinvg207.github.io/rr-rooms/ // 1) split any “accidentally merged” rooms var raw = roomsResult.Value; var splitRaw = SplitMergedRooms(raw); - var rrRooms = splitRaw - .Select(room => new RrRoom - { - Id = room.Id, - Game = room.Game, - Created = room.Created, - Type = room.Type, - Suspend = room.Suspend, - Host = room.Host, - Rk = room.Rk, - Players = room.Players.ToDictionary( - kv => kv.Key, - kv => - { - var p = kv.Value; - return new RrPlayer - { - Count = p.Count, - Pid = p.Pid, - Name = p.Name, - ConnMap = p.ConnMap, - ConnFail = p.ConnFail, - Suspend = p.Suspend, - Fc = p.Fc, - Ev = p.Ev, - Eb = p.Eb, - BadgeVariants = whWzService.GetBadges(p.Fc), - Mii = p - .Mii.Select(mii => - { - var bytes = Convert.FromBase64String(mii.Data); - var des = MiiSerializer.Deserialize(bytes); - return des.IsSuccess ? des.Value : new Mii(); - }) - .ToList(), - }; - } - ), - }) - .ToList(); + var rrRooms = splitRaw.Select(room => MapRoom(room, whWzService, leaderboardByPid, leaderboardByFriendCode)).ToList(); CurrentRooms = rrRooms; } - private static List SplitMergedRooms(List rooms) + private static RrRoom MapRoom( + RwfcRoomStatusRoom room, + IWhWzDataSingletonService whWzService, + IReadOnlyDictionary leaderboardByPid, + IReadOnlyDictionary leaderboardByFriendCode + ) + { + return new() + { + Id = room.Id, + Created = room.Created, + Type = room.Type, + Suspend = room.Suspend, + Rk = room.Rk, + Players = room.Players.Select(p => MapPlayer(p, whWzService, leaderboardByPid, leaderboardByFriendCode)).ToList(), + }; + } + + private static RrPlayer MapPlayer( + RwfcRoomStatusPlayer p, + IWhWzDataSingletonService whWzService, + IReadOnlyDictionary leaderboardByPid, + IReadOnlyDictionary leaderboardByFriendCode + ) + { + Mii? mii = null; + if (p.Mii is not null && !string.IsNullOrWhiteSpace(p.Mii.Data)) + { + try + { + var bytes = Convert.FromBase64String(p.Mii.Data); + var des = MiiSerializer.Deserialize(bytes); + if (des.IsSuccess) + mii = des.Value; + } + catch + { + // ignore invalid base64/serialization + } + } + + var friendCode = p.FriendCode ?? string.Empty; + + var leaderboardEntry = GetLeaderboardEntry(p, friendCode, leaderboardByPid, leaderboardByFriendCode); + + return new() + { + Pid = p.Pid, + Name = p.Name ?? string.Empty, + FriendCode = friendCode, + Vr = p.Vr, + Br = p.Br, + IsOpenHost = p.IsOpenHost, + IsSuspended = p.IsSuspended, + ConnectionMap = p.ConnectionMap ?? [], + Mii = mii, + BadgeVariants = whWzService.GetBadges(friendCode), + LeaderboardRank = leaderboardEntry?.Rank ?? leaderboardEntry?.ActiveRank, + }; + } + + private static RwfcLeaderboardEntry? GetLeaderboardEntry( + RwfcRoomStatusPlayer player, + string friendCode, + IReadOnlyDictionary leaderboardByPid, + IReadOnlyDictionary leaderboardByFriendCode + ) + { + if (leaderboardByPid.TryGetValue(player.Pid, out var byPid)) + return byPid; + + if (!string.IsNullOrWhiteSpace(friendCode) && leaderboardByFriendCode.TryGetValue(friendCode, out var byFriendCode)) + return byFriendCode; + + return null; + } + + private static List SplitMergedRooms(List rooms) { - var output = new List(); + var output = new List(); foreach (var room in rooms) { - var keys = room.Players.Keys.ToList(); - var n = keys.Count; + var n = room.Players.Count; // build adjacency of “two‐way” connections var adj = Enumerable.Range(0, n).Select(_ => new List()).ToArray(); + var anyEdgeAdded = false; for (var i = 0; i < n; i++) { - var map = room.Players[keys[i]].ConnMap; - for (var j = 0; j < map.Length; j++) + var mapList = room.Players[i].ConnectionMap; + if (mapList is null || mapList.Count == 0) + continue; + + // /api/roomstatus: connectionMap is usually length n (including self), but can vary. + var includesSelf = mapList.Count == n; + var excludesSelf = mapList.Count == n - 1; + + if (!includesSelf && !excludesSelf) + continue; + + for (var j = 0; j < mapList.Count; j++) { - if (map[j] == '0') + var entry = mapList[j]; + var c = string.IsNullOrEmpty(entry) ? '0' : entry[0]; + if (c == '0') + continue; + + if (includesSelf && j == i) + continue; + + var other = includesSelf ? j : (j >= i ? j + 1 : j); + if (other < 0 || other >= n) continue; - var other = j >= i ? j + 1 : j; // only add if we’ll later see the reverse link adj[i].Add(other); + anyEdgeAdded = true; } } + // If we have no connectivity information, don't attempt to split. + if (!anyEdgeAdded) + { + output.Add(room); + continue; + } + // find connected components var seen = new bool[n]; var components = new List>(); @@ -142,16 +227,15 @@ private static List SplitMergedRooms(List rooms) if (components.Count > 1) { output.AddRange( - components.Select(comp => new RwfcRoom + components.Select(comp => new RwfcRoomStatusRoom { Id = room.Id, - Game = room.Game, - Created = room.Created, Type = room.Type, - Suspend = room.Suspend, + Created = room.Created, Host = room.Host, Rk = room.Rk, - Players = comp.ToDictionary(idx => keys[idx], idx => room.Players[keys[idx]]), + Suspend = room.Suspend, + Players = comp.Select(idx => room.Players[idx]).ToList(), }) ); } diff --git a/WheelWizard/Utilities/Mockers/RrPlayerFactory.cs b/WheelWizard/Utilities/Mockers/RrPlayerFactory.cs index dc78ae89..92fba54e 100644 --- a/WheelWizard/Utilities/Mockers/RrPlayerFactory.cs +++ b/WheelWizard/Utilities/Mockers/RrPlayerFactory.cs @@ -14,16 +14,15 @@ public override RrPlayer Create(int? seed = null) var rand = Rand(seed); return new() { - Count = "1", Pid = playerId.ToString(), Name = $"Player {playerId}", - ConnMap = "0", - ConnFail = "0", - Suspend = "0", - Fc = FriendCodeFactory.Instance.Create(), - Ev = ((int)(rand.NextDouble() * 9999)).ToString(), - Eb = ((int)(rand.NextDouble() * 9999)).ToString(), - Mii = MiiFactory.Instance.CreateMultiple(1, seed).ToList(), + FriendCode = FriendCodeFactory.Instance.Create(), + Vr = (int)(rand.NextDouble() * 99999), + Br = (int)(rand.NextDouble() * 9999), + IsOpenHost = false, + IsSuspended = false, + ConnectionMap = ["0"], + Mii = MiiFactory.Instance.Create(seed), }; } } diff --git a/WheelWizard/Utilities/Mockers/RrRoomFactory.cs b/WheelWizard/Utilities/Mockers/RrRoomFactory.cs index bcae2303..4e85aa0a 100644 --- a/WheelWizard/Utilities/Mockers/RrRoomFactory.cs +++ b/WheelWizard/Utilities/Mockers/RrRoomFactory.cs @@ -13,16 +13,18 @@ public override RrRoom Create(int? seed = null) { var rand = Rand(seed); var playerCount = (int)(rand.NextDouble() * 12); - var players = RrPlayerFactory.Instance.CreateAsDictionary(playerCount, seed); + var players = RrPlayerFactory.Instance.CreateMultiple(playerCount, seed).ToList(); var isPrivate = (int)(rand.NextDouble() * 3) == 0; + + if (players.Count > 0) + players[0].IsOpenHost = true; + return new() { Id = _roomCount++.ToString(), - Game = "mariokartwii", Created = DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(30)), Type = isPrivate ? "private" : "public", Suspend = false, - Host = players.First().Value.Name, Rk = "vs_10", Players = players, }; diff --git a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml index 2ceee171..b0a3f24d 100644 --- a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml +++ b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml @@ -9,31 +9,36 @@ + Vr="5000" + IsOpenHost="True" + HasBadges="True" + IsTopPlayer="True" + TopLabel="TOP 3" /> + Vr="5000" + HasBadges="False" + IsTopPlayer="True" + TopLabel="TOP 12" /> @@ -42,7 +47,7 @@ - + - - - - + + + + + + + - - - - - - + + + + + + diff --git a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs index 43692c74..969ac2b6 100644 --- a/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs +++ b/WheelWizard/Views/Components/WhWzLibrary/PlayerListItem.axaml.cs @@ -10,12 +10,39 @@ public class PlayerListItem : TemplatedControl { public static readonly StyledProperty HasBadgesProperty = AvaloniaProperty.Register(nameof(HasBadges)); + public static readonly StyledProperty IsOpenHostProperty = AvaloniaProperty.Register(nameof(IsOpenHost)); + + public static readonly StyledProperty IsTopPlayerProperty = AvaloniaProperty.Register(nameof(IsTopPlayer)); + + public static readonly StyledProperty TopLabelProperty = AvaloniaProperty.Register( + nameof(TopLabel), + string.Empty + ); + public bool HasBadges { get => GetValue(HasBadgesProperty); set => SetValue(HasBadgesProperty, value); } + public bool IsOpenHost + { + get => GetValue(IsOpenHostProperty); + set => SetValue(IsOpenHostProperty, value); + } + + public bool IsTopPlayer + { + get => GetValue(IsTopPlayerProperty); + set => SetValue(IsTopPlayerProperty, value); + } + + public string TopLabel + { + get => GetValue(TopLabelProperty); + set => SetValue(TopLabelProperty, value); + } + public static readonly StyledProperty MiiProperty = AvaloniaProperty.Register(nameof(Mii)); public Mii? Mii diff --git a/WheelWizard/Views/Pages/FriendsPage.axaml.cs b/WheelWizard/Views/Pages/FriendsPage.axaml.cs index 6b1208e2..22fe3d91 100644 --- a/WheelWizard/Views/Pages/FriendsPage.axaml.cs +++ b/WheelWizard/Views/Pages/FriendsPage.axaml.cs @@ -157,7 +157,7 @@ private void ViewRoom_OnClick(string friendCode) { foreach (var room in RRLiveRooms.Instance.CurrentRooms) { - if (room.Players.All(player => player.Value.Fc != friendCode)) + if (room.Players.All(player => player.FriendCode != friendCode)) continue; NavigationManager.NavigateTo(room); diff --git a/WheelWizard/Views/Pages/RoomDetailsPage.axaml b/WheelWizard/Views/Pages/RoomDetailsPage.axaml index 228c729d..e684d8ee 100644 --- a/WheelWizard/Views/Pages/RoomDetailsPage.axaml +++ b/WheelWizard/Views/Pages/RoomDetailsPage.axaml @@ -65,13 +65,10 @@ - + - @@ -79,6 +76,7 @@ + + IsOpenHost="{Binding IsOpenHost}" + HasBadges="{Binding HasBadges}" + IsTopPlayer="{Binding IsTopLeaderboardPlayer}" + TopLabel="{Binding TopLabel}" /> diff --git a/WheelWizard/Views/Pages/RoomDetailsPage.axaml.cs b/WheelWizard/Views/Pages/RoomDetailsPage.axaml.cs index 0d11a01a..3c29ceef 100644 --- a/WheelWizard/Views/Pages/RoomDetailsPage.axaml.cs +++ b/WheelWizard/Views/Pages/RoomDetailsPage.axaml.cs @@ -52,7 +52,7 @@ public RoomDetailsPage() InitializeComponent(); DataContext = this; Room = RrRoomFactory.Instance.Create(); // Create a fake room for design-time preview - PlayersList = new(Room.Players.Values); + PlayersList = new(Room.Players); } public RoomDetailsPage(RrRoom room) @@ -61,7 +61,7 @@ public RoomDetailsPage(RrRoom room) DataContext = this; Room = room; - PlayersList = new(Room.Players.Values); + PlayersList = new(Room.Players); RRLiveRooms.Instance.Subscribe(this); Unloaded += RoomsDetailPage_Unloaded; @@ -83,7 +83,7 @@ public void OnUpdate(RepeatedTaskManager sender) Room = room; PlayersList.Clear(); - foreach (var p in room.Players.Values) + foreach (var p in room.Players) { PlayersList.Add(p); } @@ -95,7 +95,7 @@ private void CopyFriendCode_OnClick(object sender, RoutedEventArgs e) { if (PlayersListView.SelectedItem is not RrPlayer selectedPlayer) return; - TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(selectedPlayer.Fc); + TopLevel.GetTopLevel(this)?.Clipboard?.SetTextAsync(selectedPlayer.FriendCode); ViewUtils.ShowSnackbar(Phrases.SnackbarSuccess_CopiedFC); } diff --git a/WheelWizard/Views/Pages/RoomsPage.axaml b/WheelWizard/Views/Pages/RoomsPage.axaml index 2adc4ff2..401423d0 100644 --- a/WheelWizard/Views/Pages/RoomsPage.axaml +++ b/WheelWizard/Views/Pages/RoomsPage.axaml @@ -144,11 +144,13 @@ + IsOpenHost="{Binding IsOpenHost}" + HasBadges="{Binding HasBadges}" + IsTopPlayer="{Binding IsTopLeaderboardPlayer}" + TopLabel="{Binding TopLabel}" /> diff --git a/WheelWizard/Views/Pages/RoomsPage.axaml.cs b/WheelWizard/Views/Pages/RoomsPage.axaml.cs index bd8a7178..23b6ff2c 100644 --- a/WheelWizard/Views/Pages/RoomsPage.axaml.cs +++ b/WheelWizard/Views/Pages/RoomsPage.axaml.cs @@ -77,9 +77,10 @@ private void PerformSearch(string? query) Players.Clear(); var matchingPlayers = Rooms - .SelectMany(r => r.Players.Values) + .SelectMany(r => r.Players) .Where(p => - p.Name.Contains(query, StringComparison.OrdinalIgnoreCase) || p.Fc.Contains(query, StringComparison.OrdinalIgnoreCase) + p.Name.Contains(query, StringComparison.OrdinalIgnoreCase) + || p.FriendCode.Contains(query, StringComparison.OrdinalIgnoreCase) ) .Distinct() .ToList(); @@ -125,7 +126,7 @@ private void PlayerView_SelectionChanged(object? sender, SelectionChangedEventAr if (listBox.SelectedItem is not RrPlayer selectedRoom) return; - var room = Rooms.FirstOrDefault(r => r.Players.ContainsValue(selectedRoom)); + var room = Rooms.FirstOrDefault(r => r.Players.Any(p => p.Equals(selectedRoom))); NavigationManager.NavigateTo(room); listBox.SelectedItem = null; diff --git a/WheelWizard/Views/Pages/UserProfilePage.axaml.cs b/WheelWizard/Views/Pages/UserProfilePage.axaml.cs index 1d89fc9b..86d18c2c 100644 --- a/WheelWizard/Views/Pages/UserProfilePage.axaml.cs +++ b/WheelWizard/Views/Pages/UserProfilePage.axaml.cs @@ -262,7 +262,7 @@ private void ViewRoom_OnClick(object? sender, RoutedEventArgs e) { foreach (var room in RRLiveRooms.Instance.CurrentRooms) { - if (room.Players.All(player => player.Value.Fc != currentPlayer?.FriendCode)) + if (room.Players.All(player => player.FriendCode != currentPlayer?.FriendCode)) continue; NavigationManager.NavigateTo(room);