Merge branch 'LBPUnion:main' into main

This commit is contained in:
Toru the Red Fox 2022-07-26 10:21:18 +01:00 committed by GitHub
commit bc5c0a1b27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 183 additions and 47 deletions

View file

@ -29,7 +29,7 @@ public class ClientConfigurationController : ControllerBase
HostString hostname = this.Request.Host;
return this.Ok
(
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels false\nAllowModeratedPoppetItems false\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 240.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 60.0\nTIMEOUT_DIVE_IN_TOTAL 300.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\nAllowOnlineCreate true" +
"ProbabilityOfPacketDelay 0.0\nMinPacketDelayFrames 0\nMaxPacketDelayFrames 3\nProbabilityOfPacketDrop 0.0\nEnableFakeConditionsForLoopback true\nNumberOfFramesPredictionAllowedForNonLocalPlayer 1000\nEnablePrediction true\nMinPredictedFrames 0\nMaxPredictedFrames 10\nAllowGameRendCameraSplit true\nFramesBeforeAgressiveCatchup 30\nPredictionPadSides 200\nPredictionPadTop 200\nPredictionPadBottom 200\nShowErrorNumbers true\nAllowModeratedLevels false\nAllowModeratedPoppetItems false\nTIMEOUT_WAIT_FOR_JOIN_RESPONSE_FROM_PREV_PARTY_HOST 50.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_HOST 30.0\nTIMEOUT_WAIT_FOR_CHANGE_LEVEL_PARTY_MEMBER 45.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN_FRIEND 15.0\nTIMEOUT_WAIT_FOR_CONNECTION_FROM_HOST 30.0\nTIMEOUT_WAIT_FOR_ROOM_ID_TO_JOIN 60.0\nTIMEOUT_WAIT_FOR_GET_NUM_PLAYERS_ONLINE 60.0\nTIMEOUT_WAIT_FOR_SIGNALLING_CONNECTIONS 120.0\nTIMEOUT_WAIT_FOR_PARTY_DATA 60.0\nTIME_TO_WAIT_FOR_LEAVE_MESSAGE_TO_COME_BACK 20.0\nTIME_TO_WAIT_FOR_FOLLOWING_REQUESTS_TO_ARRIVE 30.0\nTIMEOUT_WAIT_FOR_FINISHED_MIGRATING_HOST 30.0\nTIMEOUT_WAIT_FOR_PARTY_LEADER_FINISH_JOINING 45.0\nTIMEOUT_WAIT_FOR_QUICKPLAY_LEVEL 60.0\nTIMEOUT_WAIT_FOR_PLAYERS_TO_JOIN 30.0\nTIMEOUT_WAIT_FOR_DIVE_IN_PLAYERS 240.0\nTIMEOUT_WAIT_FOR_FIND_BEST_ROOM 60.0\nTIMEOUT_DIVE_IN_TOTAL 300.0\nTIMEOUT_WAIT_FOR_SOCKET_CONNECTION 120.0\nTIMEOUT_WAIT_FOR_REQUEST_RESOURCE_MESSAGE 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_GET_RESOURCE_LIST 120.0\nTIMEOUT_WAIT_FOR_CLIENT_TO_LOAD_RESOURCES 120.0\nTIMEOUT_WAIT_FOR_LOCAL_CLIENT_TO_SAVE_GAME_STATE 30.0\nTIMEOUT_WAIT_FOR_ADD_PLAYERS_TO_TAKE 30.0\nTIMEOUT_WAIT_FOR_UPDATE_FROM_CLIENT 90.0\nTIMEOUT_WAIT_FOR_HOST_TO_GET_RESOURCE_LIST 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_SAVE_GAME_STATE 60.0\nTIMEOUT_WAIT_FOR_HOST_TO_ADD_US 30.0\nTIMEOUT_WAIT_FOR_UPDATE 60.0\nTIMEOUT_WAIT_FOR_REQUEST_JOIN 50.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_PRESENCE 60.0\nTIMEOUT_WAIT_FOR_AUTOJOIN_CONNECTION 120.0\nSECONDS_BETWEEN_PINS_AWARDED_UPLOADS 300.0\nEnableKeepAlive true\nAllowVoIPRecordingPlayback true\nOverheatingThresholdDisallowMidgameJoin 0.95\nMaxCatchupFrames 3\nMaxLagBeforeShowLoading 23\nMinLagBeforeHideLoading 30\nLagImprovementInflectionPoint -1.0\nFlickerThreshold 2.0\nClosedDemo2014Version 1\nClosedDemo2014Expired false\nEnablePlayedFilter true\nEnableCommunityDecorations true\nGameStateUpdateRate 10.0\nGameStateUpdateRateWithConsumers 1.0\nDisableDLCPublishCheck false\nEnableDiveIn true\nEnableHackChecks false\nAllowOnlineCreate true\n" +
$"TelemetryServer {hostname}\n" +
$"CDNHostName {hostname}\n" +
$"ShowLevelBoos {ServerConfiguration.Instance.UserGeneratedContentLimits.BooingEnabled.ToString().ToLower()}\n"

View file

@ -4,6 +4,7 @@ using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Levels;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.PlayerData;
using LBPUnion.ProjectLighthouse.PlayerData.Profiles;
using LBPUnion.ProjectLighthouse.Serialization;
@ -82,34 +83,67 @@ public class PublishController : ControllerBase
GameToken gameToken = userAndToken.Value.Item2;
Slot? slot = await this.getSlotFromBody();
if (slot == null) return this.BadRequest();
if (slot == null)
{
Logger.Warn("Rejecting level upload, slot is null", LogArea.Publish);
return this.BadRequest();
}
if (slot.Location == null) return this.BadRequest();
if (slot.Location == null)
{
Logger.Warn("Rejecting level upload, slot location is null", LogArea.Publish);
return this.BadRequest();
}
if (slot.Description.Length > 500) return this.BadRequest();
if (slot.Description.Length > 512)
{
Logger.Warn($"Rejecting level upload, description too long ({slot.Description.Length} characters)", LogArea.Publish);
return this.BadRequest();
}
if (slot.Name.Length > 64) return this.BadRequest();
if (slot.Name.Length > 64)
{
Logger.Warn($"Rejecting level upload, title too long ({slot.Name.Length} characters)", LogArea.Publish);
return this.BadRequest();
}
if (slot.Resources.Any(resource => !FileHelper.ResourceExists(resource)))
{
Logger.Warn("Rejecting level upload, missing resource(s)", LogArea.Publish);
return this.BadRequest();
}
LbpFile? rootLevel = LbpFile.FromHash(slot.RootLevel);
if (rootLevel == null) return this.BadRequest();
if (rootLevel == null)
{
Logger.Warn("Rejecting level upload, unable to find rootLevel", LogArea.Publish);
return this.BadRequest();
}
if (rootLevel.FileType != LbpFileType.Level) return this.BadRequest();
if (rootLevel.FileType != LbpFileType.Level)
{
Logger.Warn("Rejecting level upload, rootLevel is not a level", LogArea.Publish);
return this.BadRequest();
}
// Republish logic
if (slot.SlotId != 0)
{
Slot? oldSlot = await this.database.Slots.Include(s => s.Location).FirstOrDefaultAsync(s => s.SlotId == slot.SlotId);
if (oldSlot == null) return this.NotFound();
if (oldSlot == null)
{
Logger.Warn("Rejecting level republish, wasn't able to find old slot", LogArea.Publish);
return this.NotFound();
}
if (oldSlot.Location == null) throw new ArgumentNullException();
if (oldSlot.CreatorId != user.UserId) return this.BadRequest();
if (oldSlot.CreatorId != user.UserId)
{
Logger.Warn("Rejecting level republish, old level not owned by current user", LogArea.Publish);
return this.BadRequest();
}
oldSlot.Location.X = slot.Location.X;
oldSlot.Location.Y = slot.Location.Y;
@ -166,7 +200,8 @@ public class PublishController : ControllerBase
if (user.GetUsedSlotsForGame(gameToken.GameVersion) > ServerConfiguration.Instance.UserGeneratedContentLimits.EntitledSlots)
{
return this.StatusCode(403, "");
Logger.Warn("Rejecting level upload, too many published slots", LogArea.Publish);
return this.BadRequest();
}
//TODO: parse location in body
@ -197,6 +232,8 @@ public class PublishController : ControllerBase
"New level published!",
$"**{user.Username}** just published a new level: [**{slot.Name}**]({ServerConfiguration.Instance.ExternalUrl}/slot/{slot.SlotId})\n{slot.Description}"
);
Logger.Success($"Successfully published level {slot.Name} (id: {slot.SlotId}) by {user.Username} (id: {user.UserId})", LogArea.Publish);
return this.Ok(slot.Serialize(gameToken.GameVersion));
}

View file

@ -46,4 +46,17 @@ public class RoomVisualizerController : ControllerBase
return this.Redirect("/debug/roomVisualizer");
#endif
}
[HttpGet("createRoomsWithDuplicatePlayers")]
public async Task<IActionResult> CreateRoomsWithDuplicatePlayers()
{
#if !DEBUG
return this.NotFound();
#else
List<int> users = await this.database.Users.OrderByDescending(_ => EF.Functions.Random()).Take(1).Select(u => u.UserId).ToListAsync();
RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2, Platform.PS3);
RoomHelper.CreateRoom(users, GameVersion.LittleBigPlanet2, Platform.PS3);
return this.Redirect("/debug/roomVisualizer");
#endif
}
}

View file

@ -1,5 +1,4 @@
using LBPUnion.ProjectLighthouse.Files;
using LBPUnion.ProjectLighthouse.Helpers;
using Microsoft.AspNetCore.Mvc;
using IOFile = System.IO.File;

View file

@ -44,6 +44,10 @@
<div class="ui blue button">Create Fake Room</div>
</a>
<a href="/debug/roomVisualizer/createRoomsWithDuplicatePlayers">
<div class="ui blue button">Create Rooms With Duplicate Players</div>
</a>
<a href="/debug/roomVisualizer/deleteRooms">
<div class="ui red button">Nuke all rooms</div>
</a>

View file

@ -11,11 +11,16 @@
<script>
function onSubmit(form) {
if (document.referrer !== null && document.referrer !== "") {
const url = new URL(document.referrer);
if (url.pathname !== "/logout" && url.pathname !== "/login")
document.getElementById("redirect").value = url.pathname;
}
const passwordInput = document.getElementById("password");
const passwordSubmit = document.getElementById("password-submit");
passwordSubmit.value = sha256(passwordInput.value);
return true;
}
</script>
@ -32,6 +37,7 @@
<form class="ui form" onsubmit="return onSubmit(this)" method="post">
@Html.AntiForgeryToken()
<input type="hidden" id="redirect" name="redirect">
<div class="field">
<label>Username</label>

View file

@ -22,7 +22,7 @@ public class LoginForm : BaseLayout
public string? Error { get; private set; }
[UsedImplicitly]
public async Task<IActionResult> OnPost(string username, string password)
public async Task<IActionResult> OnPost(string username, string password, string redirect)
{
if (string.IsNullOrWhiteSpace(username))
{
@ -105,9 +105,19 @@ public class LoginForm : BaseLayout
if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired");
if (ServerConfiguration.Instance.Mail.MailEnabled && !user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail");
return this.RedirectToPage(nameof(LandingPage));
if (string.IsNullOrWhiteSpace(redirect))
{
return this.RedirectToPage(nameof(LandingPage));
}
return this.Redirect(redirect);
}
[UsedImplicitly]
public IActionResult OnGet() => this.Page();
public IActionResult OnGet()
{
if (this.Database.UserFromWebRequest(this.Request) != null)
return this.RedirectToPage(nameof(LandingPage));
return this.Page();
}
}

View file

@ -1,6 +1,17 @@
@using LBPUnion.ProjectLighthouse.Configuration
@if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
{
<div class="h-captcha" data-sitekey="@ServerConfiguration.Instance.Captcha.SiteKey"></div>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
@switch (ServerConfiguration.Instance.Captcha.Type)
{
case CaptchaType.HCaptcha:
<div class="h-captcha" data-sitekey="@ServerConfiguration.Instance.Captcha.SiteKey"></div>
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
break;
case CaptchaType.ReCaptcha:
<div class="g-recaptcha" data-sitekey="@ServerConfiguration.Instance.Captcha.SiteKey"></div>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
break;
default:
throw new ArgumentOutOfRangeException();
}
}

View file

@ -48,7 +48,14 @@
int rating = comment.ThumbsUp - comment.ThumbsDown;
<div style="display: flex" id="@comment.CommentId">
<div class="voting">
@{
string style = "";
if (Model.User?.UserId == comment.PosterUserId)
{
style = "visibility: hidden";
}
}
<div class="voting" style="@(style)">
<a href="@url/rateComment?commentId=@(comment.CommentId)&rating=@(comment.YourThumb == 1 ? 0 : 1)">
<i class="fitted @(comment.YourThumb == 1 ? "green" : "grey") arrow up link icon" style="display: block"></i>
</a>

View file

@ -37,7 +37,9 @@
}
<div>
<img src="~/assets/slotCardOverlay.png" style="min-width: @(size)px; width: @(size)px; height: @(size)px; pointer-events: none; position: absolute">
<img class="cardIcon slotCardIcon" src="/gameAssets/@iconHash" style="min-width: @(size)px; width: @(size)px; height: @(size)px">
<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;"
onerror="this.onerror='';this.src='/gameAssets/@ServerConfiguration.Instance.WebsiteConfiguration.MissingIconHash'">
</div>
<div class="cardStats">
@if (!mini)
@ -80,7 +82,7 @@
@if (Model.GameVersion == GameVersion.LittleBigPlanet1)
{
<i class="yellow star icon" title="LBP1 Stars"></i>
<span>@Model.RatingLBP1</span>
<span>@(Math.Round(Model.RatingLBP1 * 10) / 10)</span>
}
</div>
<p>

View file

@ -0,0 +1,16 @@
namespace LBPUnion.ProjectLighthouse.Configuration;
/// <summary>
/// The service to be used for presenting captchas to the user.
/// </summary>
public enum CaptchaType
{
/// <summary>
/// A privacy-first captcha. https://www.hcaptcha.com/
/// </summary>
HCaptcha,
/// <summary>
/// A captcha service by Google. https://developers.google.com/recaptcha/
/// </summary>
ReCaptcha,
}

View file

@ -2,11 +2,10 @@ namespace LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories;
public class CaptchaConfiguration
{
// TODO: support recaptcha, not just hcaptcha
// use an enum to define which captcha services can be used?
// LBPUnion.ProjectLighthouse.Types.Settings.CaptchaService
public bool CaptchaEnabled { get; set; }
public CaptchaType Type { get; set; } = CaptchaType.HCaptcha;
public string SiteKey { get; set; } = "";
public string Secret { get; set; } = "";

View file

@ -23,7 +23,7 @@ public class ServerConfiguration
// You can use an ObsoleteAttribute instead. Make sure you set it to error, though.
//
// Thanks for listening~
public const int CurrentConfigVersion = 6;
public const int CurrentConfigVersion = 7;
#region Meta

View file

@ -15,7 +15,21 @@ namespace LBPUnion.ProjectLighthouse.Extensions;
public static class RequestExtensions
{
static RequestExtensions()
{
Uri captchaUri = ServerConfiguration.Instance.Captcha.Type switch
{
CaptchaType.HCaptcha => new Uri("https://hcaptcha.com"),
CaptchaType.ReCaptcha => new Uri("https://www.google.com/recaptcha/api/"),
_ => throw new ArgumentOutOfRangeException(),
};
client = new HttpClient
{
BaseAddress = captchaUri,
};
}
#region Mobile Checking
// yoinked and adapted from https://stackoverflow.com/a/68641796
@ -32,10 +46,7 @@ public static class RequestExtensions
#region Captcha
private static readonly HttpClient client = new()
{
BaseAddress = new Uri("https://hcaptcha.com"),
};
private static readonly HttpClient client;
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
private static async Task<bool> verifyCaptcha(string token)
@ -48,7 +59,7 @@ public static class RequestExtensions
new("response", token),
};
HttpResponseMessage response = await client.PostAsync("/siteverify", new FormUrlEncodedContent(payload));
HttpResponseMessage response = await client.PostAsync("siteverify", new FormUrlEncodedContent(payload));
response.EnsureSuccessStatusCode();
@ -63,7 +74,14 @@ public static class RequestExtensions
{
if (ServerConfiguration.Instance.Captcha.CaptchaEnabled)
{
bool gotCaptcha = request.Form.TryGetValue("h-captcha-response", out StringValues values);
string keyName = ServerConfiguration.Instance.Captcha.Type switch
{
CaptchaType.HCaptcha => "h-captcha-response",
CaptchaType.ReCaptcha => "g-recaptcha-response",
_ => throw new ArgumentOutOfRangeException(),
};
bool gotCaptcha = request.Form.TryGetValue(keyName, out StringValues values);
if (!gotCaptcha) return false;
if (!await verifyCaptcha(values[0])) return false;

View file

@ -36,6 +36,8 @@ public static class MatchHelper
return recentlyDivedIn.Contains(otherUserId);
}
public static bool ClearUserRecentDiveIns(int userId) => UserRecentlyDivedIn.Remove(userId);
// This is the function used to show people how laughably awful LBP's protocol is. Beware.
public static IMatchCommand? Deserialize(string data)
{

View file

@ -21,4 +21,5 @@ public enum LogArea
Redis,
Command,
Admin,
Publish,
}

View file

@ -1,4 +1,5 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@ -47,10 +48,10 @@ public class RoomHelper
return null;
}
IEnumerable<Room> rooms = Rooms;
Random random = new();
IEnumerable<Room> rooms = Rooms.OrderBy(_ => random.Next());
rooms = rooms.OrderBy(r => r.IsLookingForPlayers);
rooms = rooms.Where(r => r.RoomVersion == roomVersion).ToList();
if (platform != null) rooms = rooms.Where(r => r.RoomPlatform == platform).ToList();
@ -137,6 +138,12 @@ public class RoomHelper
return response;
}
if (user != null)
{
MatchHelper.ClearUserRecentDiveIns(user.UserId);
Logger.Info($"Cleared {user.Username} (id: {user.UserId})'s recent dive-ins", LogArea.Match);
}
return null;
}
@ -220,12 +227,19 @@ public class RoomHelper
roomsToUpdate.Add(room);
}
// DO NOT REMOVE ROOMS BEFORE THIS POINT!
// this will cause the room to be added back to the database
foreach (Room room in roomsToUpdate)
{
rooms.Update(room);
}
// Delete old rooms based on host
if (hostId != null)
{
try
{
rooms.RemoveAll(r => r.HostId == hostId);
rooms.RemoveAll(r => r.PlayerIds.Contains((int)hostId));
}
catch
{
@ -233,19 +247,16 @@ public class RoomHelper
}
}
// Remove players in this new room from other rooms
// Remove rooms containing players in this new room
if (newRoom != null)
foreach (Room room in rooms)
{
if (room == newRoom) continue;
foreach (int newRoomPlayer in newRoom.PlayerIds) room.PlayerIds.RemoveAll(p => p == newRoomPlayer);
roomsToUpdate.Add(room);
}
foreach (Room room in roomsToUpdate)
{
rooms.Update(room);
foreach (Room room in rooms.Where(room => room != newRoom))
{
foreach (int newRoomPlayer in newRoom.PlayerIds)
{
if (room.PlayerIds.Contains(newRoomPlayer)) rooms.Remove(room);
}
}
}
rooms.RemoveAll(r => r.PlayerIds.Count == 0); // Remove empty rooms

View file

@ -20,11 +20,11 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.2" />
<PackageReference Include="Redis.OM" Version="0.2.1" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="YamlDotNet" Version="11.2.1" />
<PackageReference Include="YamlDotNet" Version="12.0.0" />
</ItemGroup>
<ItemGroup>

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After