Skip to content

Commit ca51bcb

Browse files
authored
Level (re)publish process rework (#935)
This rewrites a few parts in the `/lbp/publish` endpoint method, aswell as the `AddLevel` and `UpdateLevel` methods it calls to be more similar to the creation/updating process of other UGC (playlists for example). For example, the endpoint method doesn't pass a `GameLevel` object to the database methods anymore, validation is now only done in the endpoint method while creating and modifying `GameLevel` objects is only done in the database methods. If I understood it correctly, this also gets rid of reflection for those methods. This PR doesn't include anything for API endpoint and database methods. This also gets rid of no longer nessesary attributes and methods in `GameLevelRequest` and fixes levels to be properly marked as modded if their contents get updated to contain something modded.
2 parents 3e60afc + 9812de8 commit ca51bcb

File tree

9 files changed

+198
-367
lines changed

9 files changed

+198
-367
lines changed

Refresh.Database/GameDatabaseContext.Levels.cs

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,34 @@ public partial class GameDatabaseContext // Levels
2727
.Include(s => s.Level.Publisher)
2828
.Include(s => s.Level.Publisher!.Statistics);
2929

30-
public bool AddLevel(GameLevel level)
30+
public GameLevel AddLevel(ISerializedPublishLevel createInfo, TokenGame game, GameUser publisher)
3131
{
32-
if (level.Title is { Length: > UgcLimits.TitleLimit })
33-
level.Title = level.Title[..UgcLimits.TitleLimit];
34-
35-
if (level.Description is { Length: > UgcLimits.DescriptionLimit })
36-
level.Description = level.Description[..UgcLimits.DescriptionLimit];
37-
38-
if (level.Publisher == null) throw new InvalidOperationException("Cannot create a level without a publisher");
39-
4032
DateTimeOffset timestamp = this._time.Now;
41-
level.PublishDate = timestamp;
42-
level.UpdateDate = timestamp;
33+
34+
GameLevel level = new()
35+
{
36+
Title = createInfo.Title,
37+
Description = createInfo.Description,
38+
IconHash = createInfo.IconHash,
39+
LocationX = createInfo.Location.X,
40+
LocationY = createInfo.Location.Y,
41+
RootResource = createInfo.RootResource,
42+
IsLocked = createInfo.IsLocked,
43+
IsCopyable = createInfo.IsCopyable == 1,
44+
IsSubLevel = createInfo.IsSubLevel,
45+
MinPlayers = createInfo.MinPlayers,
46+
MaxPlayers = createInfo.MaxPlayers,
47+
LevelType = GameLevelTypeExtensions.FromGameString(createInfo.LevelType),
48+
RequiresMoveController = createInfo.RequiresMoveController,
49+
IsAdventure = createInfo.IsAdventure,
50+
EnforceMinMaxPlayers = createInfo.EnforceMinMaxPlayers,
51+
SameScreenGame = createInfo.SameScreenGame,
52+
BackgroundGuid = createInfo.BackgroundGuid,
53+
Publisher = publisher,
54+
GameVersion = game,
55+
PublishDate = timestamp,
56+
UpdateDate = timestamp,
57+
};
4358

4459
this.ApplyLevelMetadataFromAttributes(level);
4560
this.GameLevels.Add(level);
@@ -62,7 +77,7 @@ public bool AddLevel(GameLevel level)
6277
});
6378
}
6479

65-
return true;
80+
return level;
6681
}
6782

6883
public GameLevel GetStoryLevelById(int id)
@@ -156,55 +171,38 @@ public void UpdateLevelLocations(IEnumerable<ISerializedEditLevelLocation> locat
156171
this.AddErrorNotification("Level updates failed", $"Failed to update {failedUpdates} out of {locations.Count()} level locations.", updatingUser);
157172
}
158173
}
159-
160-
public GameLevel? UpdateLevel(GameLevel newLevel, GameUser author)
161-
{
162-
if (newLevel.Title is { Length: > UgcLimits.TitleLimit })
163-
newLevel.Title = newLevel.Title[..UgcLimits.TitleLimit];
164174

165-
if (newLevel.Description is { Length: > UgcLimits.DescriptionLimit })
166-
newLevel.Description = newLevel.Description[..UgcLimits.DescriptionLimit];
167-
168-
// Verify if this level is able to be republished
169-
GameLevel? oldLevel = this.GetLevelById(newLevel.LevelId);
170-
if (oldLevel == null) return null;
171-
172-
Debug.Assert(oldLevel.Publisher != null);
173-
if (oldLevel.Publisher.UserId != author.UserId) return null;
174-
175-
// All checks passed, let's start by retaining some information from the old level
176-
newLevel.Publisher = author;
177-
newLevel.PublishDate = oldLevel.PublishDate;
178-
newLevel.DateTeamPicked = oldLevel.DateTeamPicked;
179-
newLevel.IsReUpload = oldLevel.IsReUpload;
180-
newLevel.OriginalPublisher = oldLevel.OriginalPublisher;
181-
182-
// If the actual contents of the level haven't changed, extract some extra information
183-
if (oldLevel.RootResource == newLevel.RootResource)
184-
{
185-
newLevel.GameVersion = oldLevel.GameVersion;
186-
newLevel.UpdateDate = oldLevel.UpdateDate;
187-
}
188-
// If we're changing the actual level, update other things
189-
else
190-
{
191-
newLevel.UpdateDate = this._time.Now; // Set the last modified date
192-
}
193-
194-
// Now newLevel is set up to replace oldLevel.
195-
// If information is lost here, then that's probably a bug.
196-
// Update the level's properties in the database
197-
PropertyInfo[] userProps = typeof(GameLevel).GetProperties();
198-
foreach (PropertyInfo prop in userProps)
175+
public GameLevel UpdateLevel(ISerializedPublishLevel updateInfo, GameLevel level, TokenGame game = TokenGame.LittleBigPlanet1)
176+
{
177+
level.Title = updateInfo.Title;
178+
level.Description = updateInfo.Description;
179+
level.IconHash = updateInfo.IconHash;
180+
level.LocationX = updateInfo.Location.X;
181+
level.LocationY = updateInfo.Location.Y;
182+
level.IsLocked = updateInfo.IsLocked;
183+
level.IsCopyable = updateInfo.IsCopyable == 1;
184+
level.IsSubLevel = updateInfo.IsSubLevel;
185+
level.MinPlayers = updateInfo.MinPlayers;
186+
level.MaxPlayers = updateInfo.MaxPlayers;
187+
level.LevelType = GameLevelTypeExtensions.FromGameString(updateInfo.LevelType);
188+
level.RequiresMoveController = updateInfo.RequiresMoveController;
189+
level.IsAdventure = updateInfo.IsAdventure;
190+
level.EnforceMinMaxPlayers = updateInfo.EnforceMinMaxPlayers;
191+
level.SameScreenGame = updateInfo.SameScreenGame;
192+
level.BackgroundGuid = updateInfo.BackgroundGuid;
193+
194+
// If we're changing the actual contents of the level, update the game version and update date aswell
195+
if (updateInfo.RootResource != level.RootResource)
199196
{
200-
if (!prop.CanWrite || !prop.CanRead) continue;
201-
prop.SetValue(oldLevel, prop.GetValue(newLevel));
197+
level.UpdateDate = this._time.Now;
198+
level.GameVersion = game;
199+
level.RootResource = updateInfo.RootResource;
202200
}
203201

204-
this.ApplyLevelMetadataFromAttributes(newLevel);
205-
this.CreateRevisionForLevel(newLevel, author);
202+
this.ApplyLevelMetadataFromAttributes(level);
203+
this.CreateRevisionForLevel(level, level.Publisher);
206204
this.SaveChanges();
207-
return oldLevel;
205+
return level;
208206
}
209207

210208
public GameLevel? UpdateLevel(IApiEditLevelRequest body, GameLevel level, GameUser? updatingUser)

Refresh.Database/GameDatabaseContext.Notifications.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,10 @@ public void AddErrorNotification(string title, string text, GameUser user)
3030
this.AddNotification(title, text, user, "exclamation-circle");
3131
}
3232

33-
public void AddPublishFailNotification(string reason, GameLevel body, GameUser user)
34-
{
35-
this.AddPublishFailNotification(reason, body.Title, user);
36-
}
37-
3833
public void AddPublishFailNotification(string reason, string levelTitle, GameUser user)
3934
{
40-
this.AddErrorNotification("Publish failed", $"The level '{levelTitle}' failed to publish. {reason}", user);
35+
string title = string.IsNullOrWhiteSpace(levelTitle) ? "Unnamed Level" : levelTitle;
36+
this.AddErrorNotification("Publish failed", $"The level '{title}' failed to publish. {reason}", user);
4137
}
4238

4339
private const string LoginFail = "Log-in failure";
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Refresh.Database.Models;
2+
3+
namespace Refresh.Database.Query;
4+
5+
public interface ISerializedPublishLevel
6+
{
7+
string Title { get; set; }
8+
string Description { get; set; }
9+
string IconHash { get; set; }
10+
GameLocation Location { get; set; }
11+
string RootResource { get; set; }
12+
int MinPlayers { get; set; }
13+
int MaxPlayers { get; set; }
14+
bool EnforceMinMaxPlayers { get; set; }
15+
bool SameScreenGame { get; set; }
16+
string LevelType { get; set; }
17+
bool IsLocked { get; set; }
18+
bool IsSubLevel { get; set; }
19+
int IsCopyable { get; set; }
20+
bool RequiresMoveController { get; set; }
21+
bool IsAdventure { get; set; }
22+
string? BackgroundGuid { get; set; }
23+
}
Lines changed: 15 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,37 @@
11
using System.Xml.Serialization;
22
using Refresh.Database.Models;
33
using Refresh.Database.Models.Levels;
4-
using Refresh.Database.Models.Users;
5-
using Refresh.Interfaces.Game.Types.UserData;
4+
using Refresh.Database.Query;
65

76
namespace Refresh.Interfaces.Game.Endpoints.DataTypes.Request;
87

98
[XmlRoot("slot")]
109
[XmlType("slot")]
11-
public class GameLevelRequest
10+
public class GameLevelRequest : ISerializedPublishLevel
1211
{
13-
[XmlElement("id")] public required int LevelId { get; set; }
14-
15-
[XmlElement("isAdventurePlanet")] public required bool IsAdventure { get; set; }
12+
// Not included if its not a republish, in that case default to 0 (invalid ID)
13+
[XmlElement("id")] public int LevelId { get; set; }
1614

17-
[XmlElement("name")] public required string Title { get; set; }
18-
[XmlElement("icon")] public required string IconHash { get; set; }
19-
[XmlElement("description")] public required string Description { get; set; }
20-
[XmlElement("location")] public required GameLocation Location { get; set; }
15+
[XmlElement("name")] public string Title { get; set; } = "";
16+
[XmlElement("icon")] public string IconHash { get; set; } = "0";
17+
[XmlElement("description")] public string Description { get; set; } = "";
18+
[XmlElement("location")] public GameLocation Location { get; set; } = GameLocation.Zero;
2119

22-
[XmlElement("game")] public required int GameVersion { get; set; }
2320
[XmlElement("rootLevel")] public required string RootResource { get; set; }
21+
[XmlElement("isAdventurePlanet")] public bool IsAdventure { get; set; }
2422

25-
[XmlElement("firstPublished")] public required long PublishDate { get; set; } // unix seconds
26-
[XmlElement("lastUpdated")] public required long UpdateDate { get; set; }
27-
28-
[XmlElement("minPlayers")] public required int MinPlayers { get; set; }
29-
[XmlElement("maxPlayers")] public required int MaxPlayers { get; set; }
30-
[XmlElement("enforceMinMaxPlayers")] public required bool EnforceMinMaxPlayers { get; set; }
23+
[XmlElement("minPlayers")] public int MinPlayers { get; set; } = 1;
24+
[XmlElement("maxPlayers")] public int MaxPlayers { get; set; } = 4;
25+
[XmlElement("enforceMinMaxPlayers")] public bool EnforceMinMaxPlayers { get; set; }
3126

32-
[XmlElement("sameScreenGame")] public required bool SameScreenGame { get; set; }
33-
34-
[XmlAttribute("type")] public string Type { get; set; } = "user";
27+
[XmlElement("sameScreenGame")] public bool SameScreenGame { get; set; }
3528

36-
[XmlElement("npHandle")] public SerializedUserHandle Handle { get; set; } = null!;
37-
3829
[XmlArray("customRewards")]
3930
[XmlArrayItem("customReward")]
40-
public required List<GameSkillReward> SkillRewards { get; set; }
31+
public List<GameSkillReward> SkillRewards { get; set; } = [];
4132

4233
[XmlElement("resource")] public List<string> XmlResources { get; set; } = new();
43-
[XmlElement("leveltype")] public string? LevelType { get; set; } = "";
34+
[XmlElement("leveltype")] public string LevelType { get; set; } = "";
4435

4536
[XmlElement("initiallyLocked")] public bool IsLocked { get; set; }
4637
[XmlElement("isSubLevel")] public bool IsSubLevel { get; set; }
@@ -50,30 +41,4 @@ public class GameLevelRequest
5041
[XmlElement("backgroundGUID")] public string? BackgroundGuid { get; set; }
5142

5243
[XmlArray("slots")] public GameLevelRequest[]? Slots { get; set; }
53-
54-
public GameLevel ToGameLevel(GameUser publisher) =>
55-
new()
56-
{
57-
LevelId = this.LevelId,
58-
IsAdventure = this.IsAdventure,
59-
Title = this.Title,
60-
IconHash = this.IconHash,
61-
Description = this.Description,
62-
LocationX = this.Location.X,
63-
LocationY = this.Location.Y,
64-
RootResource = this.RootResource,
65-
PublishDate = DateTimeOffset.FromUnixTimeMilliseconds(this.PublishDate),
66-
UpdateDate = DateTimeOffset.FromUnixTimeMilliseconds(this.UpdateDate),
67-
MinPlayers = this.MinPlayers,
68-
MaxPlayers = this.MaxPlayers,
69-
EnforceMinMaxPlayers = this.EnforceMinMaxPlayers,
70-
SameScreenGame = this.SameScreenGame,
71-
Publisher = publisher,
72-
LevelType = GameLevelTypeExtensions.FromGameString(this.LevelType),
73-
IsLocked = this.IsLocked,
74-
IsSubLevel = this.IsSubLevel,
75-
IsCopyable = this.IsCopyable == 1,
76-
RequiresMoveController = this.RequiresMoveController,
77-
BackgroundGuid = this.BackgroundGuid,
78-
};
7944
}

0 commit comments

Comments
 (0)