Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ public async Task<IActionResult> PlayLevel(string slotType, int slotId)
return this.BadRequest();
}

if (token.GameVersion == GameVersion.LittleBigPlanet3)
{
await this.database.RecordRecentlyPlayedLevel(token.UserId, slotId);
}

await this.database.SaveChangesAsync();

return this.Ok();
Expand Down Expand Up @@ -134,4 +139,4 @@ public async Task<IActionResult> EnterLevel(string slotType, int slotId)

return this.Ok();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,12 @@ public async Task<IActionResult> GenresAndSearches()

List<GameCategory> categories = new();

SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);

foreach (Category category in CategoryHelper.Categories.Where(c => !string.IsNullOrWhiteSpace(c.Name))
.Skip(Math.Max(0, pageData.PageStart - 1))
.Take(Math.Min(pageData.PageSize, pageData.MaxElements))
.ToList())
{
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token, defaultToCurrentGame: category.DefaultToCurrentGame);
int numResults = results > 0 ? 1 : 0;
categories.Add(await category.Serialize(this.database, token, queryBuilder, numResults));
results--;
Expand All @@ -82,10 +81,11 @@ public async Task<IActionResult> GetCategorySlots(string endpointName)

Logger.Debug("Found category " + category, LogArea.Category);

SlotQueryBuilder queryBuilder = this.FilterFromRequest(token);
SlotQueryBuilder queryBuilder = this.FilterFromRequest(token, defaultToCurrentGame: category.DefaultToCurrentGame);

GenericSerializableList returnList = category switch
{
RecommendedCategory rc => await this.GetRecommendedCategory(rc, token, queryBuilder, pageData),
SlotCategory gc => await this.GetSlotCategory(gc, token, queryBuilder, pageData),
PlaylistCategory pc => await this.GetPlaylistCategory(pc, token, pageData),
UserCategory uc => await this.GetUserCategory(uc, token, pageData),
Expand All @@ -95,6 +95,21 @@ public async Task<IActionResult> GetCategorySlots(string endpointName)
return this.Ok(returnList);
}

private async Task<GenericSerializableList> GetRecommendedCategory(RecommendedCategory recommendedCategory, GameTokenEntity token, SlotQueryBuilder queryBuilder, PaginationData pageData)
{
IQueryable<RecommendedCategory.ScoredSlot> recommendations = recommendedCategory.GetScoredItems(this.database, token, queryBuilder);

pageData.TotalElements = await recommendations.CountAsync();

recommendations = recommendations.ApplyPagination(pageData);

List<ILbpSerializable> slots = (await recommendations.ToListAsync())
.Select(recommendation => RecommendedCategory.CreateSerializableSlot(recommendation, token))
.ToList();

return new GenericSerializableList(slots, pageData);
}

private async Task<GenericSerializableList> GetUserCategory(UserCategory userCategory, GameTokenEntity token, PaginationData pageData)
{
int totalUsers = await userCategory.GetItems(this.database, token).CountAsync();
Expand All @@ -121,53 +136,62 @@ private async Task<GenericSerializableList> GetPlaylistCategory(PlaylistCategory

private async Task<GenericSerializableList> GetSlotCategory(SlotCategory slotCategory, GameTokenEntity token, SlotQueryBuilder queryBuilder, PaginationData pageData)
{
int totalSlots = await slotCategory.GetItems(this.database, token, queryBuilder).CountAsync();
pageData.TotalElements = totalSlots;
IQueryable<SlotEntity> slotQuery = slotCategory.GetItems(this.database, token, queryBuilder).ApplyPagination(pageData);
IQueryable<SlotEntity> slotQuery = slotCategory.GetItems(this.database, token, queryBuilder);

if (bool.TryParse(this.Request.Query["includePlayed"], out bool includePlayed) && !includePlayed)
{
slotQuery = slotQuery.Select(s => new SlotMetadata
{
Slot = s,
Played = this.database.VisitedLevels.Any(v => v.SlotId == s.SlotId && v.UserId == token.UserId),
})
.Where(s => !s.Played)
.Select(s => s.Slot);
slotQuery = slotQuery.Where(s => !this.database.VisitedLevels.Any(v => v.SlotId == s.SlotId && v.UserId == token.UserId));
}

if (this.Request.Query.ContainsKey("sort"))
{
string sort = (string?)this.Request.Query["sort"] ?? "";
slotQuery = sort switch

if (slotCategory.Sorts.Contains(sort))
{
"relevance" => slotQuery.ApplyOrdering(new SlotSortBuilder<SlotEntity>()
.AddSort(new UniquePlaysTotalSort())
.AddSort(new LastUpdatedSort())),
"likes" => slotQuery.Select(s => new SlotMetadata
{
Slot = s,
ThumbsUp = this.database.RatedLevels.Count(r => r.SlotId == s.SlotId && r.Rating == 1),
})
.OrderByDescending(s => s.ThumbsUp)
.Select(s => s.Slot),
"hearts" => slotQuery.Select(s => new SlotMetadata
{
Slot = s,
Hearts = this.database.HeartedLevels.Count(h => h.SlotId == s.SlotId),
})
.OrderByDescending(s => s.Hearts)
.Select(s => s.Slot),
"date" => slotQuery.ApplyOrdering(new SlotSortBuilder<SlotEntity>().AddSort(new FirstUploadedSort())),
"plays" => slotQuery.ApplyOrdering(
new SlotSortBuilder<SlotEntity>().AddSort(new UniquePlaysTotalSort()).AddSort(new TotalPlaysSort())),
_ => slotQuery,
};
slotQuery = sort switch
{
"relevance" => slotQuery.ApplyOrdering(new SlotSortBuilder<SlotEntity>()
.AddSort(new UniquePlaysTotalSort())
.AddSort(new LastUpdatedSort())),

"likes" => slotQuery
.Select(s => new SlotMetadata
{
Slot = s,
ThumbsUp = this.database.RatedLevels.Count(r => r.SlotId == s.SlotId && r.Rating == 1),
})
.OrderByDescending(s => s.ThumbsUp)
.Select(s => s.Slot),

"hearts" => slotQuery
.Select(s => new SlotMetadata
{
Slot = s,
Hearts = this.database.HeartedLevels.Count(h => h.SlotId == s.SlotId),
})
.OrderByDescending(s => s.Hearts)
.Select(s => s.Slot),

"date" => slotQuery.ApplyOrdering(new SlotSortBuilder<SlotEntity>()
.AddSort(new FirstUploadedSort())),

"plays" => slotQuery.ApplyOrdering(new SlotSortBuilder<SlotEntity>()
.AddSort(new UniquePlaysTotalSort())
.AddSort(new TotalPlaysSort())),

_ => slotQuery,
};
}
}

List<ILbpSerializable> slots =
(await slotQuery.ToListAsync()).ToSerializableList<SlotEntity, ILbpSerializable>(s =>
SlotBase.CreateFromEntity(s, token));
pageData.TotalElements = await slotQuery.CountAsync();

slotQuery = slotQuery.ApplyPagination(pageData);

List<ILbpSerializable> slots = (await slotQuery.ToListAsync())
.ToSerializableList<SlotEntity, ILbpSerializable>(s => SlotBase.CreateFromEntity(s, token));

return new GenericSerializableList(slots, pageData);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static SlotQueryBuilder GetDefaultFilters(this ControllerBase controller,
.AddFilter(new HiddenSlotFilter())
.AddFilter(new SlotTypeFilter(SlotType.User));

public static SlotQueryBuilder FilterFromRequest(this ControllerBase controller, GameTokenEntity token)
public static SlotQueryBuilder FilterFromRequest(this ControllerBase controller, GameTokenEntity token, bool defaultToCurrentGame = true)
{
SlotQueryBuilder queryBuilder = new();

Expand Down Expand Up @@ -158,7 +158,7 @@ void ParseLbp3Query(string key, Action allMust, Action noneCan, Action dontCare)
.Select(s => GetGameFilter(s, token.GameVersion))
.ToArray()));
}
else
else if (defaultToCurrentGame)
{
queryBuilder.AddFilter(new GameVersionFilter(GameVersion.LittleBigPlanet3));
}
Expand All @@ -182,4 +182,4 @@ void ParseLbp3Query(string key, Action allMust, Action noneCan, Action dontCare)

return queryBuilder;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public void ConfigureServices(IServiceCollection services)
MySqlServerVersion.LatestSupportedServerVersion);
});

services.AddScoped<RoomPlayerCountService>();

IMailService mailService = ServerConfiguration.Instance.Mail.MailEnabled
? new MailQueueService(new SmtpMailSender())
: new NullMailService();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#nullable enable
using System.Linq.Expressions;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Filter;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;

namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;

public class BusiestCategory : SlotCategory
{
public override string Name { get; set; } = "Busiest";
public override string Description { get; set; } = "Levels being played right now!";
public override string IconHash { get; set; } = "g820602";
public override string Endpoint { get; set; } = "busiest";
public override string Tag => "busiest";
public override string[] Sorts { get; } = ["relevance",];

public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder)
{
Dictionary<int, int> playerCounts = RoomHelper.GetUserLevelPlayerCounts();

if (playerCounts.Count == 0)
return database.Slots.Where(_ => false);

List<int> slotIds = playerCounts.Keys.ToList();

ParameterExpression slotParameter = Expression.Parameter(typeof(SlotEntity), "slot");

MemberExpression slotIdProperty = Expression.Property(slotParameter, nameof(SlotEntity.SlotId));

Expression playerCountExpression = Expression.Constant(0);

foreach (KeyValuePair<int, int> playerCount in playerCounts)
{
playerCountExpression = Expression.Condition(Expression.Equal(slotIdProperty, Expression.Constant(playerCount.Key)), Expression.Constant(playerCount.Value), playerCountExpression);
}

Expression<Func<SlotEntity, int>> ordering = Expression.Lambda<Func<SlotEntity, int>>(playerCountExpression, slotParameter);

return database.Slots
.Where(slot =>
slotIds.Contains(slot.SlotId))
.Where(queryBuilder.Build())
.OrderByDescending(ordering)
.ThenByDescending(slot => slot.SlotId);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Configuration;

namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;

Expand All @@ -10,19 +11,32 @@ public static class CategoryHelper

static CategoryHelper()
{
Categories.Add(new TeamPicksCategory());
Categories.Add(new MostHeartedCategory());
Categories.Add(new NewestLevelsCategory());
Categories.Add(new MostPlayedCategory());
Categories.Add(new HighestRatedCategory());
Categories.Add(new MyHeartedCreatorsCategory());
Categories.Add(new MyPlaylistsCategory());
Categories.Add(new QueueCategory());
Categories.Add(new HeartedCategory());
Categories.Add(new LuckyDipCategory());
Categories.Add(new TextSearchCategory());
Dictionary<string, Func<Category>> availableCategories = new()
{
["recently_played"] = () => new RecentlyPlayedCategory(),
["recommended"] = () => new RecommendedCategory(),
["team_picks"] = () => new TeamPicksCategory(),
["most_hearted"] = () => new MostHeartedCategory(),
["newest"] = () => new NewestLevelsCategory(),
["busiest"] = () => new BusiestCategory(),
["most_played"] = () => new MostPlayedCategory(),
["my_playlists"] = () => new MyPlaylistsCategory(),
["favourite_creators"] = () => new MyHeartedCreatorsCategory(),
["queue"] = () => new QueueCategory(),
["hearted_levels"] = () => new HeartedCategory(),
["highest_rated"] = () => new HighestRatedCategory(),
["lucky_dip"] = () => new LuckyDipCategory(),
};

foreach (string categoryName in CategoryConfiguration.Instance.Categories)
{
if (availableCategories.TryGetValue(categoryName, out Func<Category>? categoryCreator))
Categories.Add(categoryCreator());
}

Categories.Add(new TextSearchCategory());
using DatabaseContext database = DatabaseContext.CreateNewInstance();
foreach (DatabaseCategoryEntity category in database.CustomCategories) Categories.Add(new CustomCategory(category));
foreach (DatabaseCategoryEntity category in database.CustomCategories)
Categories.Add(new CustomCategory(category));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ public class HeartedCategory : SlotCategory
public override string IconHash { get; set; } = "g820611";
public override string Endpoint { get; set; } = "hearted_levels";
public override string Tag => "my_hearted_levels";
public override string[] Sorts { get; } = ["relevance",];

public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder) =>
database.HeartedLevels.Where(h => h.UserId == token.UserId)
.OrderByDescending(h => h.HeartedLevelId)
.Select(h => h.Slot)
.Where(queryBuilder.Build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@
using LBPUnion.ProjectLighthouse.Filter;
using LBPUnion.ProjectLighthouse.Types.Entities.Level;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Misc;

namespace LBPUnion.ProjectLighthouse.Servers.GameServer.Types.Categories;

public class HighestRatedCategory : SlotCategory
{
public override string Name { get; set; } = "Highest Rated";
public override string Description { get; set; } = "Community Highest Rated content";
public override string Description { get; set; } = "Content with loads of thumbs up";
public override string IconHash { get; set; } = "g820603";
public override string Endpoint { get; set; } = "thumbs";
public override string Tag => "highest_rated";
public override string[] Sorts { get; } = ["likes",];
public override CategoryDefaults DefaultFilters { get; } = new()
{
DateFilterType = "thisMonth",
};
public override bool DefaultToCurrentGame => false;

public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder) =>
database.Slots.Select(s => new SlotMetadata
Expand All @@ -24,4 +31,4 @@ public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTo
.OrderByDescending(s => s.ThumbsUp)
.Select(s => s.Slot)
.Where(queryBuilder.Build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ public class LuckyDipCategory : SlotCategory
public override string Description { get; set; } = "A random selection of content";
public override string IconHash { get; set; } = "g820605";
public override string Endpoint { get; set; } = "lucky_dip";
public override string Tag => "lucky_dip";
public override string Tag => "level_of_the_day";
public override string[] Sorts { get; } = ["relevance",];
public override bool Curated => false;
public override bool DisableFilters => true;
public override bool DefaultToCurrentGame => false;

public override IQueryable<SlotEntity> GetItems(DatabaseContext database, GameTokenEntity token, SlotQueryBuilder queryBuilder)
{
queryBuilder.AddFilter(new LockedSlotFilter());
return database.Slots.Where(queryBuilder.Build())
.ApplyOrdering(new SlotSortBuilder<SlotEntity>().AddSort(new RandomFirstUploadedSort()));
}
}
}
Loading