mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 15:08:47 -07:00
feat: Add tests for room visit visibility and bootstrap logic; update versioning and image references
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using DorkNet.Server.Data;
|
||||||
|
|
||||||
|
namespace DorkNet.Server.Tests;
|
||||||
|
|
||||||
|
public sealed class CanonicalRoomBootstrapTests : IClassFixture<DorkNetServerFactory>
|
||||||
|
{
|
||||||
|
private readonly DorkNetServerFactory _factory;
|
||||||
|
|
||||||
|
public CanonicalRoomBootstrapTests(DorkNetServerFactory factory) => _factory = factory;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Bootstrap_seeds_stunt_runner_subroom_and_rec_rally_remote_image_name()
|
||||||
|
{
|
||||||
|
using var _ = _factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||||
|
|
||||||
|
await using var scope = _factory.Services.CreateAsyncScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<DorkNetDbContext>();
|
||||||
|
|
||||||
|
var stuntRunner = await db.Rooms.SingleAsync(r => r.Name == "StuntRunner");
|
||||||
|
var stuntRunnerScenes = await db.RoomScenes
|
||||||
|
.Where(s => s.RoomId == stuntRunner.Id)
|
||||||
|
.OrderBy(s => s.OrderIndex)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
Assert.Collection(
|
||||||
|
stuntRunnerScenes,
|
||||||
|
scene =>
|
||||||
|
{
|
||||||
|
Assert.Equal(0, scene.OrderIndex);
|
||||||
|
Assert.Equal("StuntRunner", scene.Name);
|
||||||
|
Assert.Equal("b7281665-a715-4051-826b-8e08e69c6172", scene.RoomSceneLocationId);
|
||||||
|
Assert.Equal("", scene.DataBlobName);
|
||||||
|
},
|
||||||
|
scene =>
|
||||||
|
{
|
||||||
|
Assert.Equal(1, scene.OrderIndex);
|
||||||
|
Assert.Equal("TheMainEvent", scene.Name);
|
||||||
|
Assert.Equal("3a636bd2-f896-424c-9225-c184522c0d87", scene.RoomSceneLocationId);
|
||||||
|
Assert.Equal("", scene.DataBlobName);
|
||||||
|
});
|
||||||
|
|
||||||
|
var recRally = await db.Rooms.SingleAsync(r => r.Name == "RecRally");
|
||||||
|
Assert.Equal("image_RecRally.jpg", recRally.ImageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,8 +38,8 @@ public sealed class DorkNetServerFactory : WebApplicationFactory<Program>
|
|||||||
["S3:Endpoint"] = "",
|
["S3:Endpoint"] = "",
|
||||||
["S3:AccessKey"] = "",
|
["S3:AccessKey"] = "",
|
||||||
["S3:SecretKey"] = "",
|
["S3:SecretKey"] = "",
|
||||||
["DorkNet:DefaultClientVersion"] = "december_2020_12_18",
|
["DorkNet:DefaultClientVersion"] = "march_2023_03_21",
|
||||||
["DorkNet:SupportedVersions:0"] = "december_2020_12_18",
|
["DorkNet:SupportedVersions:0"] = "march_2023_03_21",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
builder.ConfigureServices(services =>
|
builder.ConfigureServices(services =>
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ public sealed class GameClientEndpointContractTests :
|
|||||||
ToHttpMethod(route.Method),
|
ToHttpMethod(route.Method),
|
||||||
new Uri($"http://{host}{path}"));
|
new Uri($"http://{host}{path}"));
|
||||||
request.Headers.Accept.ParseAdd("application/json");
|
request.Headers.Accept.ParseAdd("application/json");
|
||||||
request.Headers.UserAgent.ParseAdd("RecRoom/2020.12.18");
|
request.Headers.UserAgent.ParseAdd("RecRoom/2023.03.21");
|
||||||
request.Headers.TryAddWithoutValidation("X-DorkNet-Version", "december_2020_12_18");
|
request.Headers.TryAddWithoutValidation("X-DorkNet-Version", "march_2023_03_21");
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", session.AccessToken);
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", session.AccessToken);
|
||||||
|
|
||||||
if (NeedsRequestBody(request.Method))
|
if (NeedsRequestBody(request.Method))
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ public static class GameClientSessionFactory
|
|||||||
HttpMethod.Post,
|
HttpMethod.Post,
|
||||||
new Uri($"http://auth.{apexDomain}/connect/token"));
|
new Uri($"http://auth.{apexDomain}/connect/token"));
|
||||||
tokenRequest.Headers.Accept.ParseAdd("application/json");
|
tokenRequest.Headers.Accept.ParseAdd("application/json");
|
||||||
tokenRequest.Headers.UserAgent.ParseAdd("RecRoom/2020.12.18");
|
tokenRequest.Headers.UserAgent.ParseAdd("RecRoom/2023.03.21");
|
||||||
tokenRequest.Headers.TryAddWithoutValidation("X-DorkNet-Version", "december_2020_12_18");
|
tokenRequest.Headers.TryAddWithoutValidation("X-DorkNet-Version", "march_2023_03_21");
|
||||||
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
["grant_type"] = "password",
|
["grant_type"] = "password",
|
||||||
@@ -49,8 +49,8 @@ public static class GameClientSessionFactory
|
|||||||
HttpMethod.Get,
|
HttpMethod.Get,
|
||||||
new Uri($"http://auth.{apexDomain}/connect/userinfo"));
|
new Uri($"http://auth.{apexDomain}/connect/userinfo"));
|
||||||
userInfoRequest.Headers.Accept.ParseAdd("application/json");
|
userInfoRequest.Headers.Accept.ParseAdd("application/json");
|
||||||
userInfoRequest.Headers.UserAgent.ParseAdd("RecRoom/2020.12.18");
|
userInfoRequest.Headers.UserAgent.ParseAdd("RecRoom/2023.03.21");
|
||||||
userInfoRequest.Headers.TryAddWithoutValidation("X-DorkNet-Version", "december_2020_12_18");
|
userInfoRequest.Headers.TryAddWithoutValidation("X-DorkNet-Version", "march_2023_03_21");
|
||||||
userInfoRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
userInfoRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
||||||
|
|
||||||
using var userInfoResponse = await client.SendAsync(userInfoRequest);
|
using var userInfoResponse = await client.SendAsync(userInfoRequest);
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text.Json;
|
||||||
|
using DorkNet.Server.Data;
|
||||||
|
using DorkNet.Server.Data.Entities;
|
||||||
|
using DorkNet.Server.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace DorkNet.Server.Tests;
|
||||||
|
|
||||||
|
public sealed class RoomsVisitedByTests : IClassFixture<DorkNetServerFactory>
|
||||||
|
{
|
||||||
|
private readonly DorkNetServerFactory _factory;
|
||||||
|
|
||||||
|
public RoomsVisitedByTests(DorkNetServerFactory factory) => _factory = factory;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Continue_rooms_hide_private_rooms_from_non_owners()
|
||||||
|
{
|
||||||
|
using var setupClient = ApiClient();
|
||||||
|
var owner = await GameClientSessionFactory.CreateAsync(setupClient, _factory.ApexDomain);
|
||||||
|
var visitor = await GameClientSessionFactory.CreateAsync(setupClient, _factory.ApexDomain);
|
||||||
|
|
||||||
|
var suffix = Guid.NewGuid().ToString("N")[..8];
|
||||||
|
var publicRoomId = 9_100_001L;
|
||||||
|
var privateRoomId = 9_100_002L;
|
||||||
|
var publicRoomName = $"ContinuePublic_{suffix}";
|
||||||
|
var privateRoomName = $"ContinuePrivate_{suffix}";
|
||||||
|
var now = DateTime.UtcNow;
|
||||||
|
|
||||||
|
await using (var scope = _factory.Services.CreateAsyncScope())
|
||||||
|
{
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<DorkNetDbContext>();
|
||||||
|
db.Rooms.AddRange(
|
||||||
|
BuildRoom(publicRoomId, publicRoomName, owner.PlayerId, accessibility: 1),
|
||||||
|
BuildRoom(privateRoomId, privateRoomName, owner.PlayerId, accessibility: 0));
|
||||||
|
db.RoomVisits.AddRange(
|
||||||
|
BuildVisit(publicRoomId, visitor.PlayerId, now.AddMinutes(-2)),
|
||||||
|
BuildVisit(privateRoomId, visitor.PlayerId, now.AddMinutes(-1)),
|
||||||
|
BuildVisit(privateRoomId, owner.PlayerId, now));
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var visitorClient = ApiClient(visitor);
|
||||||
|
var visitorNames = await GetContinueRoomNamesAsync(visitorClient);
|
||||||
|
|
||||||
|
Assert.Contains(publicRoomName, visitorNames);
|
||||||
|
Assert.DoesNotContain(privateRoomName, visitorNames);
|
||||||
|
|
||||||
|
using var ownerClient = ApiClient(owner);
|
||||||
|
var ownerNames = await GetContinueRoomNamesAsync(ownerClient);
|
||||||
|
|
||||||
|
Assert.Contains(privateRoomName, ownerNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClient ApiClient(GameClientSession? session = null)
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient(new() { AllowAutoRedirect = false });
|
||||||
|
client.BaseAddress = new Uri($"http://api.{_factory.ApexDomain}");
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
client.DefaultRequestHeaders.Accept.ParseAdd("application/json");
|
||||||
|
client.DefaultRequestHeaders.UserAgent.ParseAdd("RecRoom/2023.03.21");
|
||||||
|
client.DefaultRequestHeaders.TryAddWithoutValidation("X-DorkNet-Version", "march_2023_03_21");
|
||||||
|
if (session is not null)
|
||||||
|
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", session.AccessToken);
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string[]> GetContinueRoomNamesAsync(HttpClient client)
|
||||||
|
{
|
||||||
|
using var response = await client.GetAsync("/rooms/visitedby/me");
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
await using var body = await response.Content.ReadAsStreamAsync();
|
||||||
|
using var document = await JsonDocument.ParseAsync(body);
|
||||||
|
return document.RootElement
|
||||||
|
.EnumerateArray()
|
||||||
|
.Select(room => room.GetProperty("Name").GetString() ?? string.Empty)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RoomEntity BuildRoom(long id, string name, long creatorPlayerId, int accessibility) => new()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = name,
|
||||||
|
Description = name,
|
||||||
|
CreatorPlayerId = creatorPlayerId,
|
||||||
|
ImageName = RoomService.DefaultRoomImageName,
|
||||||
|
State = 0,
|
||||||
|
Accessibility = accessibility,
|
||||||
|
IsAGRoom = true,
|
||||||
|
IsDormRoom = false,
|
||||||
|
HiddenFromBrowse = false,
|
||||||
|
TagsCsv = "community",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static RoomVisitEntity BuildVisit(long roomId, long playerId, DateTime visitedAt) => new()
|
||||||
|
{
|
||||||
|
RoomId = roomId,
|
||||||
|
PlayerId = playerId,
|
||||||
|
FirstVisitAt = visitedAt,
|
||||||
|
LastVisitAt = visitedAt,
|
||||||
|
VisitCount = 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,11 +11,11 @@ namespace DorkNet.Server.Tests;
|
|||||||
public sealed class VersionCheckControllerTests
|
public sealed class VersionCheckControllerTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void December_2020_build_is_valid_with_minimal_service_config()
|
public void March_2023_build_is_valid_with_minimal_service_config()
|
||||||
{
|
{
|
||||||
var controller = CreateController();
|
var controller = CreateController();
|
||||||
|
|
||||||
var result = Assert.IsType<OkObjectResult>(controller.Check("20201210", "0"));
|
var result = Assert.IsType<OkObjectResult>(controller.Check("20230321", "0"));
|
||||||
|
|
||||||
Assert.Equal(0, ReadVersionStatus(result.Value));
|
Assert.Equal(0, ReadVersionStatus(result.Value));
|
||||||
}
|
}
|
||||||
@@ -37,9 +37,9 @@ public sealed class VersionCheckControllerTests
|
|||||||
[new Late2020VersionPlugin()],
|
[new Late2020VersionPlugin()],
|
||||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
"december_2020_12_18",
|
"march_2023_03_21",
|
||||||
},
|
},
|
||||||
"december_2020_12_18");
|
"march_2023_03_21");
|
||||||
|
|
||||||
return new VersionCheckController(
|
return new VersionCheckController(
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -2078,31 +2078,34 @@ public class RoomsController(
|
|||||||
{
|
{
|
||||||
var pid = CurrentPlayerId;
|
var pid = CurrentPlayerId;
|
||||||
if (pid is null) return Ok(Array.Empty<object>());
|
if (pid is null) return Ok(Array.Empty<object>());
|
||||||
return await VisitedBy(pid.Value);
|
return await VisitedByForViewerAsync(pid.Value, pid.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("rooms/visitedby/{playerId:long}")]
|
[HttpGet("rooms/visitedby/{playerId:long}")]
|
||||||
[HttpGet("roomserver/rooms/visitedby/{playerId:long}")]
|
[HttpGet("roomserver/rooms/visitedby/{playerId:long}")]
|
||||||
public async Task<IActionResult> VisitedBy(long playerId)
|
public async Task<IActionResult> VisitedBy(long playerId)
|
||||||
{
|
{
|
||||||
var ids = await db.RoomVisits.AsNoTracking()
|
return await VisitedByForViewerAsync(playerId, CurrentPlayerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IActionResult> VisitedByForViewerAsync(long playerId, long? viewerPlayerId)
|
||||||
|
{
|
||||||
|
var rows = await db.RoomVisits.AsNoTracking()
|
||||||
.Where(v => v.PlayerId == playerId)
|
.Where(v => v.PlayerId == playerId)
|
||||||
.OrderByDescending(v => v.LastVisitAt)
|
.Join(
|
||||||
.Select(v => v.RoomId)
|
db.Rooms.AsNoTracking(),
|
||||||
|
visit => visit.RoomId,
|
||||||
|
room => room.Id,
|
||||||
|
(visit, room) => new { visit, room })
|
||||||
|
.Where(x => x.room.Accessibility == 1 ||
|
||||||
|
(viewerPlayerId.HasValue && x.room.CreatorPlayerId == viewerPlayerId.Value))
|
||||||
|
.OrderByDescending(x => x.visit.LastVisitAt)
|
||||||
|
.Select(x => x.room)
|
||||||
.Take(100)
|
.Take(100)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
if (ids.Count == 0) return Ok(Array.Empty<object>());
|
|
||||||
|
|
||||||
var rows = await db.Rooms.AsNoTracking()
|
if (rows.Count == 0) return Ok(Array.Empty<object>());
|
||||||
.Where(r => ids.Contains(r.Id))
|
return Ok(await BuildRoomServerListAsync(rows));
|
||||||
.ToListAsync();
|
|
||||||
var byId = rows.ToDictionary(r => r.Id);
|
|
||||||
var ordered = ids
|
|
||||||
.Select(id => byId.TryGetValue(id, out var room) ? room : null)
|
|
||||||
.Where(r => r is not null)
|
|
||||||
.Select(r => r!)
|
|
||||||
.ToList();
|
|
||||||
return Ok(await BuildRoomServerListAsync(ordered));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("api/rooms/v1/moderatedby/me")]
|
[HttpGet("api/rooms/v1/moderatedby/me")]
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public static class LegacyUpgrades
|
|||||||
["Park"] = "4o5lschc01nani8xeywao622n.png",
|
["Park"] = "4o5lschc01nani8xeywao622n.png",
|
||||||
["Soccer"] = "apo2sd115tuwueyoprp8jhx7b.png",
|
["Soccer"] = "apo2sd115tuwueyoprp8jhx7b.png",
|
||||||
["StuntRunner"] = "8j4f516axpnpjgnj342t85vkd.png",
|
["StuntRunner"] = "8j4f516axpnpjgnj342t85vkd.png",
|
||||||
|
["RecRally"] = "image_RecRally.jpg",
|
||||||
};
|
};
|
||||||
|
|
||||||
var names = canonical.Keys.ToArray();
|
var names = canonical.Keys.ToArray();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"RecCenter": "50xrbhogfdae0mcgzfkomlghu.png",
|
"RecCenter": "50xrbhogfdae0mcgzfkomlghu.png",
|
||||||
"RecRoyaleSolos": "8ners1eg89dy974ixbim40sky.png",
|
"RecRoyaleSolos": "8ners1eg89dy974ixbim40sky.png",
|
||||||
"RecRoyaleSquads": "0c1qdk77fbl8c5slphl3k1t8e.png",
|
"RecRoyaleSquads": "0c1qdk77fbl8c5slphl3k1t8e.png",
|
||||||
|
"RecRally": "image_RecRally.jpg",
|
||||||
"Soccer": "apo2sd115tuwueyoprp8jhx7b.png",
|
"Soccer": "apo2sd115tuwueyoprp8jhx7b.png",
|
||||||
"StuntRunner": "8j4f516axpnpjgnj342t85vkd.png",
|
"StuntRunner": "8j4f516axpnpjgnj342t85vkd.png",
|
||||||
"TheRiseofJumbotron": "8x6qaj4nlbudf1412tupxkq9n.png"
|
"TheRiseofJumbotron": "8x6qaj4nlbudf1412tupxkq9n.png"
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class RoomService(DorkNetDbContext db)
|
|||||||
("IsleOfLostSkulls", "The Isle of Lost Skulls", "Can your pirate crew get to the Isle, defeat its fearsome guardian, and escape with the gold?", "7e01cfe0-820a-406f-b1b3-0a5bf575235c", "recroomoriginal,quest,co-op,adventure", "image_IsleOfLostSkulls.png"),
|
("IsleOfLostSkulls", "The Isle of Lost Skulls", "Can your pirate crew get to the Isle, defeat its fearsome guardian, and escape with the gold?", "7e01cfe0-820a-406f-b1b3-0a5bf575235c", "recroomoriginal,quest,co-op,adventure", "image_IsleOfLostSkulls.png"),
|
||||||
("Crescendo", "Crescendo of the Blood Moon", "Brave the haunted halls of Castle Dracula and survive the night.", "49cb8993-a956-43e2-86f4-1318f279b22a", "recroomoriginal,quest,co-op,adventure", "by3mjs9jbozpdvu6g9aje7jgz.png"),
|
("Crescendo", "Crescendo of the Blood Moon", "Brave the haunted halls of Castle Dracula and survive the night.", "49cb8993-a956-43e2-86f4-1318f279b22a", "recroomoriginal,quest,co-op,adventure", "by3mjs9jbozpdvu6g9aje7jgz.png"),
|
||||||
("StuntRunner", "Stunt Runner", "A solo platforming gauntlet — sprint, climb and dodge to reach the trophy at the top.", "b7281665-a715-4051-826b-8e08e69c6172", "recroomoriginal,sport,quest,stuntrunner,parkour", "image_StuntRunner.png"),
|
("StuntRunner", "Stunt Runner", "A solo platforming gauntlet — sprint, climb and dodge to reach the trophy at the top.", "b7281665-a715-4051-826b-8e08e69c6172", "recroomoriginal,sport,quest,stuntrunner,parkour", "image_StuntRunner.png"),
|
||||||
("RecRally", "Rec Rally", "Race across Chaparral with friends in off-road vehicles built for jumps, boosts, and tight turns.", "56193568-9ae0-498c-8a77-4df79dec91f5", "recroomoriginal,featured,sport,recrally,racing,pvp", "image_RecRally.png"),
|
("RecRally", "Rec Rally", "Race across Chaparral with friends in off-road vehicles built for jumps, boosts, and tight turns.", "56193568-9ae0-498c-8a77-4df79dec91f5", "recroomoriginal,featured,sport,recrally,racing,pvp", "image_RecRally.jpg"),
|
||||||
("Drive-In", "Rec Drive-In", "Watch movies, hang out with friends, or chill at the bar.", "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", "recroomoriginal,featured,hangout,chill", "image_DriveIn.png"),
|
("Drive-In", "Rec Drive-In", "Watch movies, hang out with friends, or chill at the bar.", "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3", "recroomoriginal,featured,hangout,chill", "image_DriveIn.png"),
|
||||||
// Hub / hang rooms — no specific gameplay, just shared spaces.
|
// Hub / hang rooms — no specific gameplay, just shared spaces.
|
||||||
("Park", "The Park", "An outdoor park with picnic tables and lawn games — a chill place to hang out.", "0a864c86-5a71-4e18-8041-8124e4dc9d98", "recroomoriginal,featured,hangout,chill", "image_Park.png"),
|
("Park", "The Park", "An outdoor park with picnic tables and lawn games — a chill place to hang out.", "0a864c86-5a71-4e18-8041-8124e4dc9d98", "recroomoriginal,featured,hangout,chill", "image_Park.png"),
|
||||||
@@ -193,7 +193,7 @@ public class RoomService(DorkNetDbContext db)
|
|||||||
["Quarry"] = "image_Quarry.png",
|
["Quarry"] = "image_Quarry.png",
|
||||||
["Clearcut"] = "image_Clearcut.png",
|
["Clearcut"] = "image_Clearcut.png",
|
||||||
["Spillway"] = "image_Spillway.png",
|
["Spillway"] = "image_Spillway.png",
|
||||||
["RecRally"] = "image_RecRally.png",
|
["RecRally"] = "image_RecRally.jpg",
|
||||||
};
|
};
|
||||||
var candidates = new[]
|
var candidates = new[]
|
||||||
{
|
{
|
||||||
@@ -371,7 +371,7 @@ public class RoomService(DorkNetDbContext db)
|
|||||||
("BowlingAlley", "Bowling Alley", "Classic ten-pin bowling. Roll strikes, beat your friends.", "ae929543-9a07-41d5-8ee9-dbbee8c36800", "recroomoriginal,sport", "image_BowlingAlley.png"),
|
("BowlingAlley", "Bowling Alley", "Classic ten-pin bowling. Roll strikes, beat your friends.", "ae929543-9a07-41d5-8ee9-dbbee8c36800", "recroomoriginal,sport", "image_BowlingAlley.png"),
|
||||||
("Crescendo", "Crescendo of the Blood Moon", "Brave the haunted halls of Castle Dracula and survive the night.", "49cb8993-a956-43e2-86f4-1318f279b22a", "recroomoriginal,quest", "by3mjs9jbozpdvu6g9aje7jgz.png"),
|
("Crescendo", "Crescendo of the Blood Moon", "Brave the haunted halls of Castle Dracula and survive the night.", "49cb8993-a956-43e2-86f4-1318f279b22a", "recroomoriginal,quest", "by3mjs9jbozpdvu6g9aje7jgz.png"),
|
||||||
("LaserTagHangar", "Laser Tag Hangar", "Teams battle in an industrial warehouse map.", "239e676c-f12f-489f-bf3a-d4c383d692c3", "recroomoriginal,sport", "image_LaserTag.png"),
|
("LaserTagHangar", "Laser Tag Hangar", "Teams battle in an industrial warehouse map.", "239e676c-f12f-489f-bf3a-d4c383d692c3", "recroomoriginal,sport", "image_LaserTag.png"),
|
||||||
("RecRally", "Rec Rally", "Race across Chaparral with friends in off-road vehicles built for jumps, boosts, and tight turns.", "56193568-9ae0-498c-8a77-4df79dec91f5", "recroomoriginal,featured,sport,recrally,racing,pvp", "image_RecRally.png"),
|
("RecRally", "Rec Rally", "Race across Chaparral with friends in off-road vehicles built for jumps, boosts, and tight turns.", "56193568-9ae0-498c-8a77-4df79dec91f5", "recroomoriginal,featured,sport,recrally,racing,pvp", "image_RecRally.jpg"),
|
||||||
("MakerRoom", "Maker Room", "A blank-walled canvas. The classic starting point for a Maker Pen build.", "a75f7547-79eb-47c6-8986-6767abcb4f92", "recroomoriginal,template,featured", "image_RecCenter.png"),
|
("MakerRoom", "Maker Room", "A blank-walled canvas. The classic starting point for a Maker Pen build.", "a75f7547-79eb-47c6-8986-6767abcb4f92", "recroomoriginal,template,featured", "image_RecCenter.png"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1229,6 +1229,21 @@ public class RoomService(DorkNetDbContext db)
|
|||||||
await EnsureScenesAsync(lasertag.Id, lasertagScenes);
|
await EnsureScenesAsync(lasertag.Id, lasertagScenes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stunt Runner's lobby contains a portal to TheMainEvent. The
|
||||||
|
// client resolves that sub-room from RoomDetails before it sends
|
||||||
|
// /goto/room/StuntRunner/TheMainEvent, so both scenes must be in
|
||||||
|
// RoomScenes, with OrderIndex 0 matching the base lobby join.
|
||||||
|
var stuntRunner = await db.Rooms.FirstOrDefaultAsync(r => r.Name == "StuntRunner");
|
||||||
|
if (stuntRunner is not null)
|
||||||
|
{
|
||||||
|
var stuntRunnerScenes = new (int OrderIndex, string Name, string Location)[]
|
||||||
|
{
|
||||||
|
(0, "StuntRunner", "b7281665-a715-4051-826b-8e08e69c6172"),
|
||||||
|
(1, "TheMainEvent", "3a636bd2-f896-424c-9225-c184522c0d87"),
|
||||||
|
};
|
||||||
|
await EnsureOrderedScenesAsync(stuntRunner.Id, stuntRunnerScenes);
|
||||||
|
}
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1261,6 +1276,50 @@ public class RoomService(DorkNetDbContext db)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Upserts canonical scenes whose order is part of the
|
||||||
|
/// client contract. Used for baked rooms where in-world portals reference
|
||||||
|
/// the scene names and the base room join must keep SubRoomId=0.</summary>
|
||||||
|
private async Task EnsureOrderedScenesAsync(
|
||||||
|
long roomId,
|
||||||
|
(int OrderIndex, string Name, string Location)[] scenes)
|
||||||
|
{
|
||||||
|
var existing = await db.RoomScenes
|
||||||
|
.Where(s => s.RoomId == roomId)
|
||||||
|
.ToListAsync();
|
||||||
|
var existingByName = existing
|
||||||
|
.GroupBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var (orderIndex, name, location) in scenes)
|
||||||
|
{
|
||||||
|
if (!existingByName.TryGetValue(name, out var scene))
|
||||||
|
{
|
||||||
|
db.RoomScenes.Add(new RoomSceneEntity
|
||||||
|
{
|
||||||
|
RoomId = roomId,
|
||||||
|
OrderIndex = orderIndex,
|
||||||
|
Name = name,
|
||||||
|
RoomSceneLocationId = location,
|
||||||
|
DataBlobName = "",
|
||||||
|
MaxPlayers = 8,
|
||||||
|
IsSandbox = false,
|
||||||
|
CanMatchmakeInto = true,
|
||||||
|
DataModifiedAt = DateTime.UtcNow,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.OrderIndex = orderIndex;
|
||||||
|
scene.RoomSceneLocationId = location;
|
||||||
|
scene.DataBlobName = "";
|
||||||
|
scene.MaxPlayers = 8;
|
||||||
|
scene.IsSandbox = false;
|
||||||
|
scene.CanMatchmakeInto = true;
|
||||||
|
if (scene.DataModifiedAt == default)
|
||||||
|
scene.DataModifiedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task TryDownloadAsync(HttpClient http, string url, string targetPath)
|
private static async Task TryDownloadAsync(HttpClient http, string url, string targetPath)
|
||||||
{
|
{
|
||||||
// Skip if already on disk — admins can delete the file to force
|
// Skip if already on disk — admins can delete the file to force
|
||||||
|
|||||||
Reference in New Issue
Block a user