diff --git a/src/Application/Characters/Commands/ResetCharacterRatingCommand.cs b/src/Application/Characters/Commands/ResetCharacterRatingCommand.cs index b704515a5..5add7ddbf 100644 --- a/src/Application/Characters/Commands/ResetCharacterRatingCommand.cs +++ b/src/Application/Characters/Commands/ResetCharacterRatingCommand.cs @@ -42,7 +42,7 @@ public async Task> Handle(ResetCharacterRatingCommand return new(CommonErrors.CharacterNotFound(req.CharacterId, req.UserId)); } - _characterService.ResetRating(character); + _characterService.ResetAllRatings(character); _db.ActivityLogs.Add(_activityLogService.CreateCharacterRatingResetLog(character.UserId, character.Id)); diff --git a/src/Application/Characters/Commands/RetireCharacterCommand.cs b/src/Application/Characters/Commands/RetireCharacterCommand.cs index 534234dfd..ab6a8cb71 100644 --- a/src/Application/Characters/Commands/RetireCharacterCommand.cs +++ b/src/Application/Characters/Commands/RetireCharacterCommand.cs @@ -45,7 +45,7 @@ public async Task> Handle(RetireCharacterCommand req, } int oldCharacterLevel = character.Level; - _characterService.ResetRating(character); + _characterService.ResetAllRatings(character); var error = _characterService.Retire(character); if (error != null) { diff --git a/src/Application/Characters/Commands/UpdateEveryCharacterCompetitiveRatingCommand.cs b/src/Application/Characters/Commands/UpdateEveryCharacterCompetitiveRatingCommand.cs index f758618a9..0514a3532 100644 --- a/src/Application/Characters/Commands/UpdateEveryCharacterCompetitiveRatingCommand.cs +++ b/src/Application/Characters/Commands/UpdateEveryCharacterCompetitiveRatingCommand.cs @@ -2,6 +2,7 @@ using Crpg.Application.Common.Mediator; using Crpg.Application.Common.Results; using Crpg.Application.Common.Services; +using Crpg.Domain.Entities.Characters; using Microsoft.EntityFrameworkCore; namespace Crpg.Application.Characters.Commands; @@ -25,7 +26,11 @@ public async Task Handle(UpdateEveryCharacterCompetitiveRatingCommand re foreach (var character in characters) { - character.Rating.CompetitiveValue = _competitiveRatingModel.ComputeCompetitiveRating(character.Rating); + foreach (CharacterStatistics statistics in character.Statistics) + { + statistics.Rating.CompetitiveValue = _competitiveRatingModel.ComputeCompetitiveRating(statistics.Rating); + } + // Trick to avoid UpdatedAt to be updated. character.UpdatedAt = character.UpdatedAt; } diff --git a/src/Application/Characters/Models/CharacterPublicViewModel.cs b/src/Application/Characters/Models/CharacterPublicViewModel.cs index df7eb5f90..7223bebc5 100644 --- a/src/Application/Characters/Models/CharacterPublicViewModel.cs +++ b/src/Application/Characters/Models/CharacterPublicViewModel.cs @@ -9,6 +9,6 @@ public record CharacterPublicViewModel : IMapFrom public int Id { get; init; } public int Level { get; init; } public CharacterClass Class { get; init; } - public CharacterRatingViewModel Rating { get; init; } = new(); + public IList Statistics { get; init; } = Array.Empty(); public UserPublicViewModel User { get; init; } = new(); } diff --git a/src/Application/Characters/Models/CharacterStatisticsViewModel.cs b/src/Application/Characters/Models/CharacterStatisticsViewModel.cs index cc7bfa199..519c23bcb 100644 --- a/src/Application/Characters/Models/CharacterStatisticsViewModel.cs +++ b/src/Application/Characters/Models/CharacterStatisticsViewModel.cs @@ -1,4 +1,4 @@ -using Crpg.Application.Common.Mappings; +using Crpg.Application.Common.Mappings; using Crpg.Domain.Entities.Characters; using Crpg.Domain.Entities.Servers; @@ -11,4 +11,5 @@ public record CharacterStatisticsViewModel : IMapFrom public int Assists { get; init; } public TimeSpan PlayTime { get; init; } public GameMode GameMode { get; init; } + public CharacterRatingViewModel Rating { get; init; } = new(); } diff --git a/src/Application/Characters/Models/GameCharacterViewModel.cs b/src/Application/Characters/Models/GameCharacterViewModel.cs index 95ccbeca0..0701cd4c6 100644 --- a/src/Application/Characters/Models/GameCharacterViewModel.cs +++ b/src/Application/Characters/Models/GameCharacterViewModel.cs @@ -1,3 +1,4 @@ +using AutoMapper; using Crpg.Application.Common.Mappings; using Crpg.Application.Items.Models; using Crpg.Domain.Entities.Characters; @@ -14,6 +15,13 @@ public record GameCharacterViewModel : IMapFrom public CharacterClass Class { get; init; } public bool ForTournament { get; init; } public CharacterCharacteristicsViewModel Characteristics { get; init; } = new(); + + public CharacterStatisticsViewModel Statistics { get; set; } = new(); public IList EquippedItems { get; init; } = Array.Empty(); - public CharacterRatingViewModel Rating { get; init; } = new(); + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(gc => gc.Statistics, opt => opt.MapFrom(c => c.Statistics.FirstOrDefault())); + } } diff --git a/src/Application/Characters/Queries/GetLeaderboardQuery.cs b/src/Application/Characters/Queries/GetLeaderboardQuery.cs index 7a64d80a8..f1c59451e 100644 --- a/src/Application/Characters/Queries/GetLeaderboardQuery.cs +++ b/src/Application/Characters/Queries/GetLeaderboardQuery.cs @@ -6,6 +6,7 @@ using Crpg.Application.Common.Results; using Crpg.Domain.Entities; using Crpg.Domain.Entities.Characters; +using Crpg.Domain.Entities.Servers; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; @@ -15,6 +16,7 @@ public record GetLeaderboardQuery : IMediatorRequest> { @@ -35,15 +37,17 @@ public async Task>> Handle(GetLeaderboard if (_cache.TryGetValue(cacheKey, out IList? results) == false) { + var requestGameMode = req.GameMode ?? Domain.Entities.Servers.GameMode.CRPGBattle; // Todo: use DistinctBy here when EfCore implements it (does not work for now: https://github.com/dotnet/efcore/issues/27470 ) var topRatedCharactersByRegion = await _db.Characters - .Include(c => c.User) - .OrderByDescending(c => c.Rating.CompetitiveValue) - .Where(c => (req.Region == null || req.Region == c.User!.Region) - && (req.CharacterClass == null || req.CharacterClass == c.Class)) - .Take(500) - .ProjectTo(_mapper.ConfigurationProvider) - .ToArrayAsync(cancellationToken); + .Include(c => c.User) + .Where(c => (req.Region == null || req.Region == c.User!.Region) + && (req.CharacterClass == null || req.CharacterClass == c.Class) + && c.Statistics.First(s => s.GameMode == requestGameMode) != null) + .OrderByDescending(c => c.Statistics.First(s => s.GameMode == requestGameMode).Rating.CompetitiveValue) + .Take(500) + .ProjectTo(_mapper.ConfigurationProvider) + .ToArrayAsync(cancellationToken); IList data = topRatedCharactersByRegion.DistinctBy(c => c.User.Id).Take(50).ToList(); @@ -71,6 +75,11 @@ private string GetCacheKey(GetLeaderboardQuery req) keys.Add(req.CharacterClass.ToString()!); } + if (req.GameMode != null) + { + keys.Add(req.GameMode.ToString()!); + } + return string.Join("::", keys); } } diff --git a/src/Application/Characters/Queries/GetUserCharacterRatingQuery.cs b/src/Application/Characters/Queries/GetUserCharacterRatingQuery.cs deleted file mode 100644 index 36a45c75c..000000000 --- a/src/Application/Characters/Queries/GetUserCharacterRatingQuery.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AutoMapper; -using AutoMapper.QueryableExtensions; -using Crpg.Application.Battles.Models; -using Crpg.Application.Characters.Models; -using Crpg.Application.Common.Interfaces; -using Crpg.Application.Common.Mediator; -using Crpg.Application.Common.Results; -using Crpg.Application.Common.Services; -using Microsoft.EntityFrameworkCore; - -namespace Crpg.Application.Characters.Queries; - -public record GetUserCharacterRatingQuery : IMediatorRequest -{ - public int CharacterId { get; init; } - public int UserId { get; init; } - - internal class Handler : IMediatorRequestHandler - { - private readonly ICrpgDbContext _db; - private readonly IMapper _mapper; - - public Handler(ICrpgDbContext db, IMapper mapper) - { - _db = db; - _mapper = mapper; - } - - public async Task> Handle(GetUserCharacterRatingQuery req, CancellationToken cancellationToken) - { - var characterRatingViewModel = await _db.Characters - .Where(c => c.Id == req.CharacterId && c.UserId == req.UserId) - .Select(c => c.Rating) - .ProjectTo(_mapper.ConfigurationProvider) - .FirstOrDefaultAsync(cancellationToken); - - return characterRatingViewModel == null - ? new(CommonErrors.CharacterNotFound(req.CharacterId, req.UserId)) - : new(characterRatingViewModel); - } - } -} diff --git a/src/Application/Common/Services/ICharacterService.cs b/src/Application/Common/Services/ICharacterService.cs index c7d25a2a6..3f6b49e26 100644 --- a/src/Application/Common/Services/ICharacterService.cs +++ b/src/Application/Common/Services/ICharacterService.cs @@ -3,6 +3,7 @@ using Crpg.Common.Helpers; using Crpg.Domain.Entities.Characters; using Crpg.Domain.Entities.Limitations; +using Crpg.Domain.Entities.Servers; namespace Crpg.Application.Common.Services; @@ -22,9 +23,11 @@ internal interface ICharacterService /// If the stats points should be redistributed. void ResetCharacterCharacteristics(Character character, bool respecialization = false); - void UpdateRating(Character character, float value, float deviation, float volatility, bool isGameUserUpdate = false); + void UpdateRating(Character character, GameMode gameMode, float value, float deviation, float volatility, bool isGameUserUpdate = false); - void ResetRating(Character character); + void ResetAllRatings(Character character); + + void ResetRating(Character character, GameMode gameMode); void ResetStatistics(Character character); @@ -56,8 +59,8 @@ public void SetValuesForNewUserStartingCharacter(Character character) character.Level = _constants.NewUserStartingCharacterLevel; character.Experience = _experienceTable.GetExperienceForLevel(character.Level); character.Class = CharacterClass.Infantry; - ResetRating(character); ResetStatistics(character); + ResetAllRatings(character); } public void SetDefaultValuesForCharacter(Character character) @@ -66,9 +69,9 @@ public void SetDefaultValuesForCharacter(Character character) character.Level = _constants.MinimumLevel; character.Experience = _experienceTable.GetExperienceForLevel(character.Level); character.ForTournament = false; - ResetCharacterCharacteristics(character); - ResetRating(character); ResetStatistics(character); + ResetCharacterCharacteristics(character); + ResetAllRatings(character); } /// @@ -96,36 +99,54 @@ public void ResetCharacterCharacteristics(Character character, bool respecializa public void ResetStatistics(Character character) { - foreach (CharacterStatistics modeStats in character.Statistics) + character.Statistics = new List(); + + foreach (GameMode gameMode in Enum.GetValues(typeof(GameMode))) { - modeStats.Kills = 0; - modeStats.Deaths = 0; - modeStats.Assists = 0; - modeStats.PlayTime = TimeSpan.Zero; + character.Statistics.Add(new CharacterStatistics + { + GameMode = gameMode, + Kills = 0, + Deaths = 0, + Assists = 0, + PlayTime = TimeSpan.Zero, + }); } } - public void UpdateRating(Character character, float value, float deviation, float volatility, bool isGameUserUpdate = false) + public void UpdateRating(Character character, GameMode gameMode, float value, float deviation, float volatility, bool isGameUserUpdate = false) { - if (character.Level == 1 - && isGameUserUpdate == true) + if (character.Level == 1 && isGameUserUpdate) { return; } - character.Rating = new CharacterRating + var statistic = character.Statistics.FirstOrDefault(s => s.GameMode == gameMode); + if (statistic != null) { - Value = value, - Deviation = deviation, - Volatility = volatility, - }; - character.Rating.CompetitiveValue = _competitiveRatingModel.ComputeCompetitiveRating(character.Rating); + statistic.Rating = new CharacterRating + { + Value = value, + Deviation = deviation, + Volatility = volatility, + }; + + statistic.Rating.CompetitiveValue = _competitiveRatingModel.ComputeCompetitiveRating(statistic.Rating); + } + } + + public void ResetAllRatings(Character character) + { + foreach (GameMode gameMode in Enum.GetValues(typeof(GameMode))) + { + ResetRating(character, gameMode); + } } - public void ResetRating(Character character) + public void ResetRating(Character character, GameMode gameMode) { - UpdateRating(character, _constants.DefaultRating, _constants.DefaultRatingDeviation, - _constants.DefaultRatingVolatility); + UpdateRating(character, gameMode, _constants.DefaultRating, _constants.DefaultRatingDeviation, + _constants.DefaultRatingVolatility); } public Error? Retire(Character character) diff --git a/src/Application/Games/Commands/GetGameUserCommand.cs b/src/Application/Games/Commands/GetGameUserCommand.cs index 803b9fc93..18c21fce6 100644 --- a/src/Application/Games/Commands/GetGameUserCommand.cs +++ b/src/Application/Games/Commands/GetGameUserCommand.cs @@ -8,6 +8,7 @@ using Crpg.Domain.Entities.Characters; using Crpg.Domain.Entities.Items; using Crpg.Domain.Entities.Limitations; +using Crpg.Domain.Entities.Servers; using Crpg.Domain.Entities.Users; using Crpg.Sdk.Abstractions; using FluentValidation; @@ -25,6 +26,7 @@ public record GetGameUserCommand : IMediatorRequest public Platform Platform { get; init; } public string PlatformUserId { get; init; } = default!; public Region Region { get; init; } + public string Instance { get; init; } = string.Empty; public class Validator : AbstractValidator { @@ -135,10 +137,11 @@ internal static readonly (string id, ItemSlot slot)[][] DefaultItemSets = private readonly IUserService _userService; private readonly ICharacterService _characterService; private readonly IActivityLogService _activityLogService; + private readonly IGameModeService _gameModeService; public Handler(ICrpgDbContext db, IMapper mapper, IDateTime dateTime, IRandom random, IUserService userService, ICharacterService characterService, - IActivityLogService activityLogService) + IActivityLogService activityLogService, IGameModeService gameModeService) { _db = db; _mapper = mapper; @@ -147,6 +150,7 @@ public Handler(ICrpgDbContext db, IMapper mapper, IDateTime dateTime, _userService = userService; _characterService = characterService; _activityLogService = activityLogService; + _gameModeService = gameModeService; } public async Task> Handle(GetGameUserCommand req, CancellationToken cancellationToken) @@ -211,6 +215,35 @@ await _db.Entry(user.ActiveCharacter) .Query() .Include(ei => ei.UserItem) .LoadAsync(cancellationToken); + + if (!Enum.TryParse(req.Instance, true, out GameModeAlias instanceAlias)) + { + instanceAlias = GameModeAlias.Z; // Default value if parsing fails. + } + + GameMode currentGameMode = _gameModeService.GameModeByInstanceAlias(instanceAlias); + + var statistics = await _db.Entry(user.ActiveCharacter) + .Collection(c => c.Statistics) + .Query() + .Where(s => s.GameMode == currentGameMode) + .Include(s => s.Rating) + .AsNoTracking() + .ToListAsync(cancellationToken); + + var statistic = statistics.FirstOrDefault(); + + if (statistic == null) + { + user.ActiveCharacter.Statistics.Add(new CharacterStatistics { GameMode = currentGameMode }); + _characterService.ResetRating(user.ActiveCharacter, currentGameMode); + await _db.SaveChangesAsync(cancellationToken); + + statistic = user.ActiveCharacter.Statistics.First(s => s.GameMode == currentGameMode); + } + + // Only load the relevant statistic for the gameMode + user.ActiveCharacter.Statistics = new List { statistic }; } var gameUser = _mapper.Map(user); diff --git a/src/Application/Games/Commands/UpdateGameUsersCommand.cs b/src/Application/Games/Commands/UpdateGameUsersCommand.cs index 7bf71f030..ba022a98b 100644 --- a/src/Application/Games/Commands/UpdateGameUsersCommand.cs +++ b/src/Application/Games/Commands/UpdateGameUsersCommand.cs @@ -56,7 +56,7 @@ public async Task> Handle(UpdateGameUsersCommand r await _db.SaveChangesAsync(cancellationToken); var charactersById = await LoadCharacters(req.Updates, cancellationToken); - List<(User user, GameUserEffectiveReward reward, List repairedItems)> results = new(req.Updates.Count); + List<(User user, GameUserEffectiveReward reward, List repairedItems, GameMode gameMode)> results = new(req.Updates.Count); foreach (var update in req.Updates) { GameMode updateGameMode = _gameModeService.GameModeByInstanceAlias(Enum.TryParse(update.Instance[^1..], ignoreCase: true, out GameModeAlias instanceAlias) ? instanceAlias : GameModeAlias.Z); @@ -66,11 +66,11 @@ public async Task> Handle(UpdateGameUsersCommand r continue; } - _characterService.UpdateRating(character, update.Rating.Value, update.Rating.Deviation, update.Rating.Volatility, isGameUserUpdate: true); var reward = GiveReward(character, update.Reward, updateGameMode); UpdateStatistics(updateGameMode, character, update.Statistics); + _characterService.UpdateRating(character, updateGameMode, update.Statistics.Rating.Value, update.Statistics.Rating.Deviation, update.Statistics.Rating.Volatility, isGameUserUpdate: true); var brokenItems = await RepairOrBreakItems(character, update.BrokenItems, cancellationToken); - results.Add((character.User!, reward, brokenItems)); + results.Add((character.User!, reward, brokenItems, updateGameMode)); } key.Status = UserUpdateStatus.Completed; @@ -79,11 +79,23 @@ public async Task> Handle(UpdateGameUsersCommand r return new(new UpdateGameUsersResult { - UpdateResults = results.Select(r => new UpdateGameUserResult + UpdateResults = results.Select(r => { - User = _mapper.Map(r.user), - EffectiveReward = r.reward, - RepairedItems = r.repairedItems, + var gameUserViewModel = _mapper.Map(r.user); + + // Only include relevant statistic in response + var relevantStatistic = r.user.ActiveCharacter?.Statistics.FirstOrDefault(s => s.GameMode == r.gameMode); + if (relevantStatistic != null) + { + gameUserViewModel.Character.Statistics = _mapper.Map(relevantStatistic); + } + + return new UpdateGameUserResult + { + User = gameUserViewModel, + EffectiveReward = r.reward, + RepairedItems = r.repairedItems, + }; }).ToArray(), }); } diff --git a/src/Application/Games/Models/GameUserUpdate.cs b/src/Application/Games/Models/GameUserUpdate.cs index feaa3472e..7f8839658 100644 --- a/src/Application/Games/Models/GameUserUpdate.cs +++ b/src/Application/Games/Models/GameUserUpdate.cs @@ -8,7 +8,6 @@ public record GameUserUpdate public int CharacterId { get; init; } public GameUserReward Reward { get; init; } = new(); public CharacterStatisticsViewModel Statistics { get; init; } = new(); - public CharacterRatingViewModel Rating { get; init; } = new(); public IList BrokenItems { get; init; } = Array.Empty(); public string Instance { get; init; } = string.Empty; } diff --git a/src/Application/System/Commands/SeedDataCommand.cs b/src/Application/System/Commands/SeedDataCommand.cs index fc3143e1c..44bf45a25 100644 --- a/src/Application/System/Commands/SeedDataCommand.cs +++ b/src/Application/System/Commands/SeedDataCommand.cs @@ -117,6 +117,7 @@ private async Task AddDevelopmentData() PlatformUserId = "76561198023558734", Platform = Platform.Steam, Name = "droob", + ActiveCharacterId = 8, Role = Role.Admin, Gold = 1000000, HeirloomPoints = 12, @@ -674,11 +675,18 @@ private async Task AddDevelopmentData() { new CharacterStatistics { - Kills = 2, - Deaths = 3, - Assists = 6, - PlayTime = new TimeSpan(0, 10, 50, 20), - GameMode = GameMode.CRPGBattle, + Kills = 1, + Deaths = 30, + Assists = 10, + PlayTime = new TimeSpan(10, 7, 5, 20), + GameMode = GameMode.CRPGDuel, + Rating = new() + { + Value = 2990, + Deviation = 350, + Volatility = 0.06f, + CompetitiveValue = 2990, + }, } }, }, @@ -731,6 +739,26 @@ private async Task AddDevelopmentData() Name = "namichar", Level = 10, Experience = 146457, + Statistics = new List + { + { + new CharacterStatistics + { + Kills = 1, + Deaths = 30, + Assists = 10, + PlayTime = new TimeSpan(10, 7, 5, 20), + GameMode = GameMode.CRPGDuel, + Rating = new() + { + Value = 2400, + Deviation = 350, + Volatility = 0.06f, + CompetitiveValue = 2400, + }, + } + }, + }, }; Character orleCharacter0 = new() { @@ -748,7 +776,14 @@ private async Task AddDevelopmentData() Deaths = 3, Assists = 6, PlayTime = new TimeSpan(365, 0, 0, 20), - GameMode = GameMode.CRPGBattle, + GameMode = GameMode.CRPGDuel, + Rating = new() + { + Value = 1900, + Deviation = 100, + Volatility = 100, + CompetitiveValue = 1900, + }, } }, }, @@ -757,13 +792,6 @@ private async Task AddDevelopmentData() Attributes = new CharacterAttributes { Points = 100 }, Skills = new CharacterSkills { Points = 100 }, }, - Rating = new() - { - Value = 50, - Deviation = 100, - Volatility = 100, - CompetitiveValue = 1900, - }, }; Character orleCharacter1 = new() @@ -797,6 +825,13 @@ private async Task AddDevelopmentData() Assists = 10, PlayTime = new TimeSpan(10, 7, 5, 20), GameMode = GameMode.CRPGBattle, + Rating = new() + { + Value = 1500, + Deviation = 350, + Volatility = 0.06f, + CompetitiveValue = 600, + }, } }, { @@ -807,16 +842,30 @@ private async Task AddDevelopmentData() Assists = 10, PlayTime = new TimeSpan(100, 3, 50, 15), GameMode = GameMode.CRPGConquest, + Rating = new() + { + Value = 1200, + Deviation = 100, + Volatility = 100, + CompetitiveValue = 1200, + }, } }, { new CharacterStatistics { - Kills = 5, - Deaths = 5, + Kills = 133, + Deaths = 7, Assists = 0, - PlayTime = new TimeSpan(0, 8, 1, 15), + PlayTime = new TimeSpan(100, 3, 50, 15), GameMode = GameMode.CRPGDuel, + Rating = new() + { + Value = 5000, + Deviation = 5, + Volatility = 0.05f, + CompetitiveValue = 5000, + }, } }, }, @@ -825,13 +874,6 @@ private async Task AddDevelopmentData() Attributes = new CharacterAttributes { Points = 100 }, Skills = new CharacterSkills { Points = 100 }, }, - Rating = new() - { - Value = 50, - Deviation = 100, - Volatility = 100, - CompetitiveValue = 1900, - }, }; Character kadseCharacter0 = new() { @@ -845,11 +887,18 @@ private async Task AddDevelopmentData() { new CharacterStatistics { - Kills = 2, - Deaths = 3, - Assists = 6, - PlayTime = new TimeSpan(365, 0, 0, 20), - GameMode = GameMode.CRPGBattle, + Kills = 1, + Deaths = 30, + Assists = 10, + PlayTime = new TimeSpan(10, 7, 5, 20), + GameMode = GameMode.CRPGDuel, + Rating = new() + { + Value = 50, + Deviation = 350, + Volatility = 0.06f, + CompetitiveValue = 50, + }, } }, }, diff --git a/src/Domain/Entities/Characters/Character.cs b/src/Domain/Entities/Characters/Character.cs index d97e872c1..63a0e512f 100644 --- a/src/Domain/Entities/Characters/Character.cs +++ b/src/Domain/Entities/Characters/Character.cs @@ -34,8 +34,6 @@ public class Character : AuditableEntity public CharacterCharacteristics Characteristics { get; set; } = new(); public IList EquippedItems { get; set; } = new List(); public IList Statistics { get; set; } = new List(); - public CharacterRating Rating { get; set; } = new(); - public User? User { get; set; } public CharacterLimitations? Limitations { get; set; } } diff --git a/src/Domain/Entities/Characters/CharacterStatistics.cs b/src/Domain/Entities/Characters/CharacterStatistics.cs index 54ab572f3..5d358e3b6 100644 --- a/src/Domain/Entities/Characters/CharacterStatistics.cs +++ b/src/Domain/Entities/Characters/CharacterStatistics.cs @@ -1,4 +1,4 @@ -using Crpg.Domain.Entities.Servers; +using Crpg.Domain.Entities.Servers; namespace Crpg.Domain.Entities.Characters; @@ -9,5 +9,6 @@ public class CharacterStatistics public int Assists { get; set; } public TimeSpan PlayTime { get; set; } public GameMode GameMode { get; set; } + public CharacterRating Rating { get; set; } = new(); public Character? Character { get; set; } } diff --git a/src/Module.Client/GUI/Brushes/CrpgHUD.xml b/src/Module.Client/GUI/Brushes/CrpgHUD.xml new file mode 100644 index 000000000..d43329d13 --- /dev/null +++ b/src/Module.Client/GUI/Brushes/CrpgHUD.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +