diff --git a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs index eb520306..1396b7fb 100644 --- a/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs +++ b/Refresh.GameServer/Database/GameDatabaseContext.Photos.cs @@ -19,15 +19,16 @@ public void UploadPhoto(SerializedPhoto photo, GameUser publisher) PlanHash = photo.PlanHash, Publisher = publisher, - LevelName = photo.Level.Title, - LevelType = photo.Level.Type, - LevelId = photo.Level.LevelId, + LevelName = photo.Level?.Title ?? "", + LevelType = photo.Level?.Type ?? "", + //If level is null, default to level ID 0 + LevelId = photo.Level?.LevelId ?? 0, TakenAt = DateTimeOffset.FromUnixTimeSeconds(Math.Clamp(photo.Timestamp, this._time.EarliestDate, this._time.TimestampSeconds)), PublishedAt = this._time.Now, }; - if (photo.Level.Type == "user") + if (photo.Level?.Type == "user") newPhoto.Level = this.GetLevelById(photo.Level.LevelId); float[] bounds = new float[SerializedPhotoSubject.FloatCount]; diff --git a/Refresh.GameServer/Database/GameDatabaseProvider.cs b/Refresh.GameServer/Database/GameDatabaseProvider.cs index 23dfd7fb..6fc65a10 100644 --- a/Refresh.GameServer/Database/GameDatabaseProvider.cs +++ b/Refresh.GameServer/Database/GameDatabaseProvider.cs @@ -32,7 +32,7 @@ protected GameDatabaseProvider(IDateTimeProvider time) this._time = time; } - protected override ulong SchemaVersion => 93; + protected override ulong SchemaVersion => 95; protected override string Filename => "refreshGameServer.realm"; @@ -159,6 +159,9 @@ protected override void Migrate(Migration migration, ulong oldVersion) { newUser.VitaPlanetsHash = "0"; } + + // In version 94, we added an option to redirect grief reports to photos + if (oldVersion < 94) newUser.RedirectGriefReportsToPhotos = false; } IQueryable? oldLevels = migration.OldRealm.DynamicApi.All("GameLevel"); diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs index a487b843..2e470d95 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Request/ApiUpdateUserRequest.cs @@ -9,5 +9,7 @@ public class ApiUpdateUserRequest public bool? PsnAuthenticationAllowed { get; set; } public bool? RpcnAuthenticationAllowed { get; set; } + public bool? RedirectGriefReportsToPhotos { get; set; } + public string? EmailAddress { get; set; } } \ No newline at end of file diff --git a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiExtendedGameUserResponse.cs b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiExtendedGameUserResponse.cs index bf0ada75..e43cdf25 100644 --- a/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiExtendedGameUserResponse.cs +++ b/Refresh.GameServer/Endpoints/ApiV3/DataTypes/Response/ApiExtendedGameUserResponse.cs @@ -27,6 +27,8 @@ public class ApiExtendedGameUserResponse : IApiResponse, IDataConvertableFrom p.Count), + TotalPlayCount = totalPlayCount, UniquePlayCount = old.UniquePlays.Count(), YayCount = old.Ratings.Count(r => r._RatingType == (int)RatingType.Yay), BooCount = old.Ratings.Count(r => r._RatingType == (int)RatingType.Boo), diff --git a/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs b/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs index c9794103..e2436470 100644 --- a/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs +++ b/Refresh.GameServer/Endpoints/Game/ReportingEndpoints.cs @@ -3,20 +3,82 @@ using Bunkum.Core.Responses; using Bunkum.Listener.Protocol; using Bunkum.Protocols.Http; +using Refresh.GameServer.Authentication; using Refresh.GameServer.Database; +using Refresh.GameServer.Extensions; +using Refresh.GameServer.Time; +using Refresh.GameServer.Types.Levels; +using Refresh.GameServer.Types.Photos; using Refresh.GameServer.Types.Report; +using Refresh.GameServer.Types.UserData; namespace Refresh.GameServer.Endpoints.Game; public class ReportingEndpoints : EndpointGroup { [GameEndpoint("grief", HttpMethods.Post, ContentType.Xml)] - public Response UploadReport(RequestContext context, GameDatabaseContext database, GameReport body) + public Response UploadReport(RequestContext context, GameDatabaseContext database, GameReport body, GameUser user, IDateTimeProvider time, Token token) { - if ((body.LevelId != 0 && database.GetLevelById(body.LevelId) == null) || body.Players is { Length: > 4 } || body.ScreenElements is { Player.Length: > 4 }) - { + GameLevel? level = database.GetLevelById(body.LevelId); + + Size imageSize = token.TokenGame switch { + TokenGame.LittleBigPlanet1 => new Size(640, 360), + TokenGame.LittleBigPlanet2 => new Size(640, 360), + TokenGame.LittleBigPlanet3 => new Size(640, 360), + TokenGame.LittleBigPlanetVita => new Size(512, 290), + TokenGame.LittleBigPlanetPSP => new Size(480, 272), + _ => throw new ArgumentOutOfRangeException(nameof(token), $"Token game {token.TokenGame} is not allowed for grief upload!"), + }; + + //If the level is specified but its invalid, return BadRequest + if (body.LevelId != 0 && level == null) return BadRequest; + + if (user.RedirectGriefReportsToPhotos) + { + List subjects = new(); + if (body.Players != null) + subjects.AddRange(body.Players.Select(player => new SerializedPhotoSubject + { + Username = player.Username, + DisplayName = player.Username, + // ReSharper disable PossibleLossOfFraction YES I KNOW THESE ARE INTEGERS + BoundsList = $"{(float)(player.Rectangle.Left - imageSize.Width / 2) / (imageSize.Width / 2)}," + + $"{(float)(player.Rectangle.Top - imageSize.Height / 2) / (imageSize.Height / 2)}," + + $"{(float)(player.Rectangle.Right - imageSize.Width / 2) / (imageSize.Width / 2)}," + + $"{(float)(player.Rectangle.Bottom - imageSize.Height / 2) / (imageSize.Height / 2)}", + })); + + string hash = context.IsPSP() ? "psp/" + body.JpegHash : body.JpegHash; + + database.UploadPhoto(new SerializedPhoto + { + Timestamp = time.TimestampSeconds, + AuthorName = user.Username, + SmallHash = hash, + MediumHash = hash, + LargeHash = hash, + PlanHash = "0", + //If the level id is 0 or we couldn't find the level null, dont fill out the `Level` field + Level = body.LevelId == 0 || level == null ? null : new SerializedPhotoLevel + { + LevelId = level.LevelId, + Title = level.Title, + Type = level.Source switch { + GameLevelSource.User => "user", + GameLevelSource.Story => "developer", + _ => throw new ArgumentOutOfRangeException(), + }, + }, + PhotoSubjects = subjects, + }, user); + + return OK; } + + //Basic validation + if (body.Players is { Length: > 4 } || body.ScreenElements is { Player.Length: > 4 }) + return BadRequest; database.AddGriefReport(body); diff --git a/Refresh.GameServer/Services/CommandService.cs b/Refresh.GameServer/Services/CommandService.cs index d6433e22..fd0c0e6a 100644 --- a/Refresh.GameServer/Services/CommandService.cs +++ b/Refresh.GameServer/Services/CommandService.cs @@ -100,6 +100,16 @@ public void HandleCommand(CommandInvocation command, GameDatabaseContext databas break; } + case "griefphotoson": { + user.RedirectGriefReportsToPhotos = true; + + break; + } + case "griefphotosoff": { + user.RedirectGriefReportsToPhotos = false; + + break; + } #if DEBUG case "tokengame": { diff --git a/Refresh.GameServer/Types/Report/GameReport.cs b/Refresh.GameServer/Types/Report/GameReport.cs index 8a855632..ab80aceb 100644 --- a/Refresh.GameServer/Types/Report/GameReport.cs +++ b/Refresh.GameServer/Types/Report/GameReport.cs @@ -51,7 +51,10 @@ public InfoBubble[] InfoBubble [XmlElement("levelId")] public int LevelId { get; set; } - + + [XmlElement("description")] + public string Description { get; set; } + [XmlElement("griefStateHash")] public string GriefStateHash { get; set; } diff --git a/Refresh.GameServer/Types/UserData/GameUser.cs b/Refresh.GameServer/Types/UserData/GameUser.cs index a2768c90..14a58392 100644 --- a/Refresh.GameServer/Types/UserData/GameUser.cs +++ b/Refresh.GameServer/Types/UserData/GameUser.cs @@ -80,6 +80,11 @@ public partial class GameUser : IRealmObject, IRateLimitUser public bool RpcnAuthenticationAllowed { get; set; } public bool PsnAuthenticationAllowed { get; set; } + /// + /// If `true`, turn all grief reports into photo uploads + /// + public bool RedirectGriefReportsToPhotos { get; set; } + [Ignored] public GameUserRole Role { get => (GameUserRole)this._Role;