From 3f4a1b0eb0fc7669a82753d5ee332bf9549d5b6d Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Wed, 3 Jun 2026 11:42:42 +0300 Subject: [PATCH] Update Crescendo image references: replace old image name with new identifier across multiple files --- .../Match/MatchPlayerController.cs | 67 +++++++++++++------ DorkNet.Server/Data/LegacyUpgrades.cs | 2 +- DorkNet.Server/Data/room_images.json | 2 +- DorkNet.Server/Services/RoomService.cs | 6 +- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/DorkNet.Server/Controllers/Match/MatchPlayerController.cs b/DorkNet.Server/Controllers/Match/MatchPlayerController.cs index 9038b4e..eb5fe97 100644 --- a/DorkNet.Server/Controllers/Match/MatchPlayerController.cs +++ b/DorkNet.Server/Controllers/Match/MatchPlayerController.cs @@ -21,6 +21,9 @@ namespace DorkNet.Server.Controllers.Match; public class MatchPlayerController( PlayerPresenceService presence, NotificationService notifications, + RoomService rooms, + PrivateInstanceService privateInstances, + IConfiguration config, DorkNetDbContext db, ILogger log) : ControllerBase { @@ -298,34 +301,58 @@ public class MatchPlayerController( /// Build a RoomInstance that matches the shape /// /goto/room/DormRoom would have produced for this player /// — used as a heartbeat fallback when presence is empty (boot - /// race, replica restart, Redis TTL expiry). Same RoomInstanceId - /// math, same PhotonRoomId pattern, same location GUID; the - /// watch's RoomInstancesAreDifferent finds the fallback identical - /// to its own cached state, so no out-of-sync. Returns null only - /// if we can't find the player's dorm row in the DB. + /// race, replica restart, Redis TTL expiry). + /// + /// CRITICAL: this MUST reproduce /goto's RoomInstance + /// byte-for-byte (RoomInstanceId, PhotonRoomId, Location, DataBlob), + /// or the watch's RoomInstancesAreDifferent fires + /// "presence heartbeat response indicates local presence is + /// out-of-sync!" and boot-loops the player. The previous version + /// computed RoomInstanceId = roomId * 100_000, but + /// GoToController stamps the dorm with + /// EnsureForDormAsync's id (0xDD0000 ^ …) — so the two + /// disagreed and every cache-miss heartbeat (e.g. right after a + /// server restart) kicked the player. We now go through the exact + /// same + + /// path + /// /goto/room/DormRoom uses, so the id math can never drift + /// again. Returns null only for anonymous callers. private async Task BuildDefaultDormPresenceAsync(int playerId) { - var dormRow = await db.Rooms.AsNoTracking() - .Where(r => r.IsDormRoom && r.CreatorPlayerId == playerId) - .Select(r => new { r.Id, r.CurrentDataBlobName, r.LocationReplicationId }) - .FirstOrDefaultAsync(); + if (playerId == 0) return null; - // Fall back to the canonical dorm template row if the player - // doesn't yet have their own dorm record — same id space - // /goto uses. - long roomId = dormRow?.Id ?? 1; - string dataBlob = dormRow?.CurrentDataBlobName ?? string.Empty; - if (string.IsNullOrWhiteSpace(dataBlob)) - dataBlob = await ResolveDataBlobForPresenceAsync(roomId, playerId); + // Same resolution /goto does: the caller's personal dorm row is + // the source of truth for RoomId + LocationReplicationId. + var personal = await rooms.EnsurePersonalDormAsync(playerId); + var roomId = personal.Id; + var location = personal.LocationReplicationId; + + var dataBlob = await ResolveDataBlobForPresenceAsync(roomId, playerId); + var photonRegion = (config["Photon:CloudRegion"] ?? "us").ToLowerInvariant(); + var photonRoomId = $"^dormroom_p{playerId}"; + + // EnsureForDormAsync owns the deterministic dorm instance-id + // formula; reusing it here guarantees the heartbeat's + // RoomInstanceId equals the one /goto handed the watch. + var dorm = await privateInstances.EnsureForDormAsync( + ownerPlayerId: playerId, + roomId: roomId, + subRoomId: 0, + baseName: "DormRoom", + location: location, + dataBlob: dataBlob, + photonRegion: photonRegion, + maxCapacity: 8, + photonRoomId: photonRoomId); return new RoomInstanceDto { - RoomInstanceId = roomId * 100_000L, + RoomInstanceId = dorm.InstanceId, RoomId = roomId, SubRoomId = 0, - Location = dormRow?.LocationReplicationId ?? "76d98498-60a1-430c-ab76-b54a29b7a163", - PhotonRegionId = "us", - PhotonRoomId = $"^dormroom_p{playerId}", + Location = location, + PhotonRegionId = photonRegion, + PhotonRoomId = photonRoomId, Name = "DormRoom", MaxCapacity = 8, IsFull = false, diff --git a/DorkNet.Server/Data/LegacyUpgrades.cs b/DorkNet.Server/Data/LegacyUpgrades.cs index 370234e..7e3becc 100644 --- a/DorkNet.Server/Data/LegacyUpgrades.cs +++ b/DorkNet.Server/Data/LegacyUpgrades.cs @@ -128,7 +128,7 @@ public static class LegacyUpgrades { bloodMoon.Name = "Crescendo"; bloodMoon.Description = "Brave the haunted halls of Castle Dracula and survive the night."; - bloodMoon.ImageName = "image_Crescendo.png"; + bloodMoon.ImageName = "by3mjs9jbozpdvu6g9aje7jgz.png"; bloodMoon.HiddenFromBrowse = false; await db.SaveChangesAsync(ct); logger.LogInformation( diff --git a/DorkNet.Server/Data/room_images.json b/DorkNet.Server/Data/room_images.json index c460707..b62e57a 100644 --- a/DorkNet.Server/Data/room_images.json +++ b/DorkNet.Server/Data/room_images.json @@ -1,7 +1,7 @@ { "3DCharades": "aj7w8q5nrfyt2ssfoovawtz4l.png", "BowlingAlley": "8gwqibu0anm4j0rw54xo2y0yd.png", - "Crescendo": "image_Crescendo.png", + "Crescendo": "by3mjs9jbozpdvu6g9aje7jgz.png", "CrimsonCauldron": "ae6yh8p6y8p3cuyqkc4l8wi4z.png", "DiscGolfLake": "86s8y4ihyhx7n9ac41gwahwze.png", "DiscGolfPropulsion": "6wvnxmrmsw98wsttajp0nreci.png", diff --git a/DorkNet.Server/Services/RoomService.cs b/DorkNet.Server/Services/RoomService.cs index 2f922b3..4b0ceae 100644 --- a/DorkNet.Server/Services/RoomService.cs +++ b/DorkNet.Server/Services/RoomService.cs @@ -104,7 +104,7 @@ public class RoomService(DorkNetDbContext db) // IsleOfLostSkulls was previously pointing at Quarry's GUID; // real Location ReplicationId is 7e01cfe0-… (scene Quest_Pirate1_additive). ("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", "image_Crescendo.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"), ("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. @@ -368,7 +368,7 @@ public class RoomService(DorkNetDbContext db) ("PerformanceHall", "Performance Hall", "A big stage for live performances. Sing, perform, or just enjoy the show.", "9932f88f-3929-43a0-a012-a40b5128e346", "recroomoriginal", "image_PerformanceHall.png"), ("EventRoom", "The Lounge", "A modular event hall with movable furniture for parties and meetings.", "a067557f-ca32-43e6-b6e5-daaec60b4f5a", "recroomoriginal", "image_EventRoom.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", "image_Crescendo.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"), ("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"), }; @@ -939,7 +939,7 @@ public class RoomService(DorkNetDbContext db) // (no UI for that yet) since the user didn't pass a custom URL. Directory.CreateDirectory(imagesDir); await TryDownloadAsync(http, "https://img.rec.net/by3mjs9jbozpdvu6g9aje7jgz.png", - Path.Combine(imagesDir, "image_Crescendo.png")); + Path.Combine(imagesDir, "by3mjs9jbozpdvu6g9aje7jgz.png")); await TryDownloadAsync(http, "https://static.wikia.nocookie.net/rec-room/images/4/48/Paintball_-_Key_Art.png/revision/latest/scale-to-width-down/1000?cb=20200513122350", Path.Combine(imagesDir, "image_PaintballKeyArt.png"));