Skip to content

Commit

Permalink
feat: add training ground gamemode (#367)
Browse files Browse the repository at this point in the history
Co-authored-by: Valeriy Sinevich <[email protected]>
  • Loading branch information
Muparadzi and or2e committed Sep 18, 2024
1 parent 9ec232f commit fdbb1a0
Show file tree
Hide file tree
Showing 87 changed files with 7,574 additions and 447 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public async Task<Result<CharacterViewModel>> 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));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task<Result<CharacterViewModel>> Handle(RetireCharacterCommand req,
}

int oldCharacterLevel = character.Level;
_characterService.ResetRating(character);
_characterService.ResetAllRatings(character);
var error = _characterService.Retire(character);
if (error != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,7 +26,11 @@ public async Task<Result> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ public record CharacterPublicViewModel : IMapFrom<Character>
public int Id { get; init; }
public int Level { get; init; }
public CharacterClass Class { get; init; }
public CharacterRatingViewModel Rating { get; init; } = new();
public IList<CharacterStatisticsViewModel> Statistics { get; init; } = Array.Empty<CharacterStatisticsViewModel>();
public UserPublicViewModel User { get; init; } = new();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Crpg.Application.Common.Mappings;
using Crpg.Application.Common.Mappings;
using Crpg.Domain.Entities.Characters;
using Crpg.Domain.Entities.Servers;

Expand All @@ -11,4 +11,5 @@ public record CharacterStatisticsViewModel : IMapFrom<CharacterStatistics>
public int Assists { get; init; }
public TimeSpan PlayTime { get; init; }
public GameMode GameMode { get; init; }
public CharacterRatingViewModel Rating { get; init; } = new();
}
10 changes: 9 additions & 1 deletion src/Application/Characters/Models/GameCharacterViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using AutoMapper;
using Crpg.Application.Common.Mappings;
using Crpg.Application.Items.Models;
using Crpg.Domain.Entities.Characters;
Expand All @@ -14,6 +15,13 @@ public record GameCharacterViewModel : IMapFrom<Character>
public CharacterClass Class { get; init; }
public bool ForTournament { get; init; }
public CharacterCharacteristicsViewModel Characteristics { get; init; } = new();

public CharacterStatisticsViewModel Statistics { get; set; } = new();
public IList<GameEquippedItemViewModel> EquippedItems { get; init; } = Array.Empty<GameEquippedItemViewModel>();
public CharacterRatingViewModel Rating { get; init; } = new();

public void Mapping(Profile profile)
{
profile.CreateMap<Character, GameCharacterViewModel>()
.ForMember(gc => gc.Statistics, opt => opt.MapFrom(c => c.Statistics.FirstOrDefault()));
}
}
23 changes: 16 additions & 7 deletions src/Application/Characters/Queries/GetLeaderboardQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -15,6 +16,7 @@ public record GetLeaderboardQuery : IMediatorRequest<IList<CharacterPublicViewMo
{
public Region? Region { get; set; }
public CharacterClass? CharacterClass { get; set; }
public GameMode? GameMode { get; set; }

internal class Handler : IMediatorRequestHandler<GetLeaderboardQuery, IList<CharacterPublicViewModel>>
{
Expand All @@ -35,15 +37,17 @@ public async Task<Result<IList<CharacterPublicViewModel>>> Handle(GetLeaderboard

if (_cache.TryGetValue(cacheKey, out IList<CharacterPublicViewModel>? 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<CharacterPublicViewModel>(_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<CharacterPublicViewModel>(_mapper.ConfigurationProvider)
.ToArrayAsync(cancellationToken);

IList<CharacterPublicViewModel> data = topRatedCharactersByRegion.DistinctBy(c => c.User.Id).Take(50).ToList();

Expand Down Expand Up @@ -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);
}
}
Expand Down
42 changes: 0 additions & 42 deletions src/Application/Characters/Queries/GetUserCharacterRatingQuery.cs

This file was deleted.

65 changes: 43 additions & 22 deletions src/Application/Common/Services/ICharacterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -22,9 +23,11 @@ internal interface ICharacterService
/// <param name="respecialization">If the stats points should be redistributed.</param>
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);

Expand Down Expand Up @@ -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)
Expand All @@ -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);
}

/// <inheritdoc />
Expand Down Expand Up @@ -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<CharacterStatistics>();

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)
Expand Down
35 changes: 34 additions & 1 deletion src/Application/Games/Commands/GetGameUserCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +26,7 @@ public record GetGameUserCommand : IMediatorRequest<GameUserViewModel>
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<GetGameUserCommand>
{
Expand Down Expand Up @@ -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;
Expand All @@ -147,6 +150,7 @@ public Handler(ICrpgDbContext db, IMapper mapper, IDateTime dateTime,
_userService = userService;
_characterService = characterService;
_activityLogService = activityLogService;
_gameModeService = gameModeService;
}

public async Task<Result<GameUserViewModel>> Handle(GetGameUserCommand req, CancellationToken cancellationToken)
Expand Down Expand Up @@ -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<CharacterStatistics> { statistic };
}

var gameUser = _mapper.Map<GameUserViewModel>(user);
Expand Down
Loading

0 comments on commit fdbb1a0

Please sign in to comment.