LittleBigPlanet 3 Adventure Support (#477)

* Baseline LBP3 Adventure slot support
VERY unsafe and hacky to use as of now, this is just testing the waters.

* ADC file type checking

* Refactor & trimming
This might need to be adjusted if any feature is found to be missing

* isAdventure added to API

* Prototype Adventure icons for Website
I am not an artist, please make this more in line with the originals.

* Override border radius for LBP3 Adventures

* Cleaning

* Remove WriteLine and unused property

* Remove unused libraries

* Handle tracking and submitting of Adventure scores

* Check for null instead of 0
Non-adventure slots will report null, not 0

* Score for adventure slot instead of main slot

* Tweaks for PR

* Identify levels for photos by level resource
Verify this doesn't break anything.

* SlotCardPartial merge with main changes

* PR resolution 2

* probably not what was wanted
Use variables for style extension

* Internal slots already properly identified

* Return line breaks to end of Slot.cs

* Remove line break added by Github

thanks

* Github.

* Make this a one-liner

* Reduce to two lines

* This can also be one line

* This can *also* be one line

* Update ProjectLighthouse.Servers.Website/Pages/Partials/SlotCardPartial.cshtml

Co-authored-by: Josh <josh@slendy.pw>

* PR changes

* Update ProjectLighthouse/Migrations/20220916141401_ScoreboardAdvSlot.cs

Co-authored-by: Josh <josh@slendy.pw>

Co-authored-by: Josh <josh@slendy.pw>
This commit is contained in:
Dagg 2022-09-20 14:08:02 -07:00 committed by GitHub
parent 83a905c8a2
commit dfd1d9b748
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 107 additions and 22 deletions

View file

@ -10,6 +10,7 @@ public struct MinimalSlot
public string Name { get; set; }
public string IconHash { get; set; }
public bool TeamPick { get; set; }
public bool IsAdventure { get; set; }
public GameVersion GameVersion { get; set; }
#if DEBUG
public long FirstUploaded { get; set; }
@ -22,6 +23,7 @@ public struct MinimalSlot
Name = slot.Name,
IconHash = slot.IconHash,
TeamPick = slot.TeamPick,
IsAdventure = slot.IsAdventurePlanet,
GameVersion = slot.GameVersion,
#if DEBUG
FirstUploaded = slot.FirstUploaded,

View file

@ -63,8 +63,12 @@ public class PhotosController : ControllerBase
{
case SlotType.User:
{
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.SlotId == photoSlot.SlotId);
if (slot != null && !string.IsNullOrEmpty(slot.RootLevel)) validLevel = true;
// We'll grab the slot by the RootLevel and see what happens from here.
Slot? slot = await this.database.Slots.FirstOrDefaultAsync(s => s.Type == SlotType.User && s.ResourceCollection.Contains(photoSlot.RootLevel));
if(slot == null) break;
if (!string.IsNullOrEmpty(slot!.RootLevel)) validLevel = true;
if (slot.IsAdventurePlanet) photoSlot.SlotId = slot.SlotId;
break;
}
case SlotType.Pod:

View file

@ -40,7 +40,8 @@ public class PublishController : ControllerBase
GameToken gameToken = userAndToken.Value.Item2;
Slot? slot = await this.getSlotFromBody();
if (slot == null) {
if (slot == null)
{
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
return this.BadRequest(); // if the level cant be parsed then it obviously cant be uploaded
}
@ -135,10 +136,21 @@ public class PublishController : ControllerBase
return this.BadRequest();
}
if (rootLevel.FileType != LbpFileType.Level)
if (!slot.IsAdventurePlanet)
{
Logger.Warn("Rejecting level upload, rootLevel is not a level", LogArea.Publish);
return this.BadRequest();
if (rootLevel.FileType != LbpFileType.Level)
{
Logger.Warn("Rejecting level upload, rootLevel is not a level", LogArea.Publish);
return this.BadRequest();
}
}
else
{
if (rootLevel.FileType != LbpFileType.Adventure)
{
Logger.Warn("Rejecting level upload, rootLevel is not a LBP 3 Adventure", LogArea.Publish);
return this.BadRequest();
}
}
GameVersion slotVersion = FileHelper.ParseLevelVersion(rootLevel);
@ -232,7 +244,7 @@ public class PublishController : ControllerBase
this.database.Slots.Add(slot);
await this.database.SaveChangesAsync();
if (user.LevelVisibility == PrivacyType.All)
{
await WebhookHelper.SendWebhook("New level published!",

View file

@ -26,7 +26,8 @@ public class ScoreController : ControllerBase
}
[HttpPost("scoreboard/{slotType}/{id:int}")]
public async Task<IActionResult> SubmitScore(string slotType, int id, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
[HttpPost("scoreboard/{slotType}/{id:int}/{childId:int}")]
public async Task<IActionResult> SubmitScore(string slotType, int id, int? childId, [FromQuery] bool lbp1 = false, [FromQuery] bool lbp2 = false, [FromQuery] bool lbp3 = false)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
@ -78,6 +79,7 @@ public class ScoreController : ControllerBase
if (slotType == "developer") id = await SlotHelper.GetPlaceholderSlotId(this.database, id, SlotType.Developer);
score.SlotId = id;
score.ChildSlotId = childId;
Slot? slot = this.database.Slots.FirstOrDefault(s => s.SlotId == score.SlotId);
if (slot == null)
@ -116,12 +118,13 @@ public class ScoreController : ControllerBase
Type = score.Type,
Points = score.Points,
SlotId = score.SlotId,
ChildSlotId = score.ChildSlotId
};
IQueryable<Score> existingScore = this.database.Scores.Where(s => s.SlotId == playerScore.SlotId)
.Where(s => s.PlayerIdCollection == playerScore.PlayerIdCollection)
.Where(s => s.Type == playerScore.Type);
.Where(s => childId != 0 || s.ChildSlotId == childId)
.Where(s => s.PlayerIdCollection == playerScore.PlayerIdCollection)
.Where(s => s.Type == playerScore.Type);
if (existingScore.Any())
{
Score first = existingScore.First(s => s.SlotId == playerScore.SlotId);
@ -137,13 +140,14 @@ public class ScoreController : ControllerBase
await this.database.SaveChangesAsync();
string myRanking = this.getScores(score.SlotId, score.Type, username, -1, 5, "scoreboardSegment");
string myRanking = this.getScores(score.SlotId, score.Type, username, -1, 5, "scoreboardSegment", childId: score.ChildSlotId);
return this.Ok(myRanking);
}
[HttpGet("friendscores/{slotType}/{slotId:int}/{type:int}")]
public async Task<IActionResult> FriendScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
[HttpGet("friendscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")]
public async Task<IActionResult> FriendScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
@ -169,12 +173,13 @@ public class ScoreController : ControllerBase
if (friendUsername != null) friendNames.Add(friendUsername);
}
return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, "scores", friendNames.ToArray()));
return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, "scores", friendNames.ToArray(), childId));
}
[HttpGet("topscores/{slotType}/{slotId:int}/{type:int}")]
[HttpGet("topscores/{slotType}/{slotId:int}/{childId:int}/{type:int}")]
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
public async Task<IActionResult> TopScores(string slotType, int slotId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
public async Task<IActionResult> TopScores(string slotType, int slotId, int? childId, int type, [FromQuery] int pageStart = -1, [FromQuery] int pageSize = 5)
{
GameToken? token = await this.database.GameTokenFromRequest(this.Request);
if (token == null) return this.StatusCode(403, "");
@ -187,7 +192,7 @@ public class ScoreController : ControllerBase
if (slotType == "developer") slotId = await SlotHelper.GetPlaceholderSlotId(this.database, slotId, SlotType.Developer);
return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize));
return this.Ok(this.getScores(slotId, type, username, pageStart, pageSize, childId: childId));
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
@ -199,7 +204,8 @@ public class ScoreController : ControllerBase
int pageStart = -1,
int pageSize = 5,
string rootName = "scores",
string[]? playerIds = null
string[]? playerIds = null,
int? childId = 0
)
{
@ -207,8 +213,9 @@ public class ScoreController : ControllerBase
// var needed for Anonymous type returned from SELECT
var rankedScores = this.database.Scores
.Where(s => s.SlotId == slotId && s.Type == type)
.AsEnumerable()
.Where(s => s.ChildSlotId == null || s.ChildSlotId == childId)
.Where(s => playerIds == null || playerIds.Any(id => s.PlayerIdCollection.Contains(id)))
.AsEnumerable()
.OrderByDescending(s => s.Points)
.ThenBy(s => s.ScoreId)
.ToList()

View file

@ -37,7 +37,8 @@
{
case SlotType.User:
<span>
in level <b><a href="/slot/@Model.SlotId">@HttpUtility.HtmlDecode(Model.Slot.Name)</a></b>
@(Model.Slot.IsAdventurePlanet ? "on an adventure in" : "in level")
<b><a href="/slot/@Model.SlotId">@HttpUtility.HtmlDecode(Model.Slot.Name)</a></b>
</span>
break;
case SlotType.Developer:

View file

@ -39,11 +39,13 @@
<div class="card">
@{
int size = isMobile || mini ? 50 : 100;
bool isAdventure = Model.IsAdventurePlanet;
string advenStyleExt = isAdventure ? "-webkit-mask-image: url(/assets/advSlotCardMask.png); -webkit-mask-size: contain; border-radius: 0%;" : "";
}
<div>
<img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute; z-index: 3">
<img src="~/assets/slotCardBackground.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; position: absolute; z-index: 1;">
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px; position: relative; z-index: 2"
<img src=@(isAdventure ? "/assets/advSlotCardOverlay.png" : "/assets/slotCardOverlay.png") style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute; z-index: 3;">
<img src="~/assets/slotCardBackground.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; position: absolute; z-index: 1; @(advenStyleExt)">
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px; position: relative; z-index: 2; @(advenStyleExt)"
onerror="this.onerror='';this.src='/gameAssets/@ServerConfiguration.Instance.WebsiteConfiguration.MissingIconHash'">
</div>
<div class="cardStats">

View file

@ -64,6 +64,7 @@ public static class FileHelper
LbpFileType.Texture => true,
LbpFileType.Script => false,
LbpFileType.Level => true,
LbpFileType.Adventure => true,
LbpFileType.Voice => true,
LbpFileType.Quest => true,
LbpFileType.Plan => true,
@ -190,6 +191,8 @@ public static class FileHelper
"FSHb" => LbpFileType.Script,
"VOPb" => LbpFileType.Voice,
"LVLb" => LbpFileType.Level,
"ADCb" => LbpFileType.Adventure,
"ADSb" => LbpFileType.Adventure,
"PLNb" => LbpFileType.Plan,
"QSTb" => LbpFileType.Quest,
_ => readAlternateHeader(reader),

View file

@ -5,6 +5,7 @@ public enum LbpFileType
Script, // .ff, FSH
Texture, // TEX
Level, // LVL
Adventure, // ADC, ADS
CrossLevel, // PRF, Cross controller level
FileArchive, // .farc, (ends with FARC)
Plan, // PLN, uploaded with levels

View file

@ -60,6 +60,9 @@ public class Slot
[XmlElement("icon")]
public string IconHash { get; set; } = "";
[XmlElement("isAdventurePlanet")]
public bool IsAdventurePlanet { get; set; }
[XmlElement("rootLevel")]
[JsonIgnore]
public string RootLevel { get; set; } = "";
@ -301,6 +304,7 @@ public class Slot
LbpSerializer.StringElement("initiallyLocked", this.InitiallyLocked) +
LbpSerializer.StringElement("isSubLevel", this.SubLevel) +
LbpSerializer.StringElement("isLBP1Only", this.Lbp1Only) +
LbpSerializer.StringElement("isAdventurePlanet", this.IsAdventurePlanet) +
LbpSerializer.StringElement("background", this.BackgroundHash) +
LbpSerializer.StringElement("shareable", this.Shareable) +
LbpSerializer.StringElement("authorLabels", this.AuthorLabels) +

View file

@ -0,0 +1,23 @@
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220916141401_ScoreboardAdvSlot")]
public partial class CreateScoreboardAdvSlot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ChildSlotId",
table: "Scores",
type: "int",
nullable: false,
defaultValue: 0);
}
}
}

View file

@ -0,0 +1,23 @@
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220918154500_AddIsAdventureColumn")]
public partial class AddisAdventureColumn : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "IsAdventurePlanet",
table: "Slots",
type: "bool",
nullable: false,
defaultValue: false);
}
}
}

View file

@ -21,6 +21,9 @@ public class Score
[ForeignKey(nameof(SlotId))]
public Slot Slot { get; set; }
[XmlIgnore]
public int? ChildSlotId { get; set; }
[XmlElement("type")]
public int Type { get; set; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB