mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
829 lines
31 KiB
C#
829 lines
31 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet;
|
|
using RadicadeServer.RecNet.Config;
|
|
using RadicadeServer.RecNet.Enums;
|
|
using RadicadeServer.RecNet.Enums.Rooms;
|
|
using RadicadeServer.RecNet.Rooms;
|
|
|
|
namespace RadicadeServer.Utils
|
|
{
|
|
public static class DefaultRooms
|
|
{
|
|
private const string AlignmentKey = "DefaultRooms.AlignmentVersion";
|
|
|
|
private const int AlignmentVersion = 1;
|
|
|
|
private const string RroCorrectionKey = "DefaultRooms.RroCorrectionVersion";
|
|
|
|
private const string RecCenterRepairKey = "DefaultRooms.RecCenterRepairVersion";
|
|
|
|
private const int RecCenterRepairVersion = 1;
|
|
|
|
private const string RecCenterRepairRoom = "RecCenter";
|
|
|
|
private const string CloningCorrectionKey = "DefaultRooms.NoCloneVersion";
|
|
|
|
private const int CloningCorrectionVersion = 1;
|
|
|
|
private const int RroCorrectionVersion = 2;
|
|
|
|
private const string ShippedRoomResetKey = "DefaultRooms.ShippedRoomResetVersion";
|
|
|
|
private const int ShippedRoomResetVersion = 3;
|
|
|
|
public static IReadOnlyList<DefaultRoom> All { get; } = new List<DefaultRoom>
|
|
{
|
|
new DefaultRoom
|
|
{
|
|
Name = "Orientation", Description = "An introductory tour of Rec Room!",
|
|
MaxPlayers = 1, SceneId = "c79709d8-a31b-48aa-9eb8-cc31ba9505e8",
|
|
Tags = Rro(), CloningAllowed = false, DisableRoomComments = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "RecCenter", Description = "A social hub to meet and mingle with friends new and old.",
|
|
MaxPlayers = 12, SceneId = "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
|
Tags = Rro(), CloningAllowed = false
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "TheRiseofJumbotron", Description = "Robot invaders threaten the galaxy! Team up with your friends and bring the laser heat!",
|
|
MaxPlayers = 4, SceneId = "acc06e66-c2d0-4361-b0cd-46246a4c455c",
|
|
Tags = RroWith("quest")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "GoldenTrophy", Description = "The goblin king stole Coach's Golden Trophy. Team up and embark on an epic quest to recover it!",
|
|
MaxPlayers = 4, SceneId = "91e16e35-f48f-4700-ab8a-a1b79e50e51b",
|
|
Tags = RroWith("quest")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Crescendo", Description = "Gather your vampire hunting crew, conquer a legendary castle, and restore peace to Rec Room!",
|
|
MaxPlayers = 3, SceneId = "49cb8993-a956-43e2-86f4-1318f279b22a",
|
|
Tags = RroWith("quest")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "CrimsonCauldron", Description = "Can your band of adventurers brave the enchanted wilds, and lift the curse of the crimson cauldron?",
|
|
MaxPlayers = 4, SceneId = "949fa41f-4347-45c0-b7ac-489129174045",
|
|
Tags = RroWith("quest")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "IsleOfLostSkulls", Description = "Can your pirate crew get to the Isle, defeat its fearsome guardian, and escape with the gold?",
|
|
MaxPlayers = 3, SceneId = "7e01cfe0-820a-406f-b1b3-0a5bf575235c",
|
|
Tags = RroWith("quest")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "StuntRunner", Description = "Sprint, climb, and wall jump in this high-speed obstacle course where every second counts!",
|
|
MaxPlayers = 12, SceneId = "b7281665-a715-4051-826b-8e08e69c6172", SubRoomName = "StuntRunner",
|
|
Tags = RroWith("sports"), CloningAllowed = false,
|
|
ExtraSubRooms = new List<DefaultSubRoom>
|
|
{
|
|
new() { Name = "TheMainEvent", SceneId = "3a636bd2-f896-424c-9225-c184522c0d87", MaxPlayers = 4 }
|
|
}
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "3DCharades", Description = "Take turns drawing, acting, and guessing funny phrases with your friends!",
|
|
MaxPlayers = 8, SceneId = "a673712c-877f-4749-b69a-4a4c6310d545", SubRoomName = "InkSpaceHome",
|
|
Tags = Rro(), DisableRoomComments = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Legacy3DCharades", Description = "Take turns drawing, acting, and guessing funny phrases with your friends!",
|
|
MaxPlayers = 8, SceneId = "4078dfed-24bb-4db7-863f-578ba48d726b",
|
|
Tags = Rro(), DisableRoomComments = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "TheInkSpace", Description = "A cozy event venue with a stage. The entire room is made of props, so customize to your heart's content!",
|
|
MaxPlayers = 8, SceneId = "1fa06e3c-c307-4c11-a91b-1fabcddb8a96",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true, DisableRoomComments = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Dodgeball", Description = "Throw dodgeballs to knock out your friends in this gym classic!",
|
|
MaxPlayers = 6, SceneId = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
|
Tags = RroWith("sports")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "DodgeballVR", Description = "Throw dodgeballs to knock out your friends in this gym classic!",
|
|
MaxPlayers = 6, SceneId = "3d474b26-26f7-45e9-9a36-9b02847d5e6f"
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Bowling", Description = "Hang out with friends, bowl a few games, eat snacks!",
|
|
MaxPlayers = 8, SceneId = "ae929543-9a07-41d5-8ee9-dbbee8c36800",
|
|
Tags = RroWith("sports")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "BowlingAlley", Description = "Lone Shoe Lanes is the hottest hangout in town.",
|
|
MaxPlayers = 8, SceneId = "ae929543-9a07-41d5-8ee9-dbbee8c36800",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Paddleball", Description = "A simple rally game between two players in a plexiglass tube with a zero-g ball.",
|
|
MaxPlayers = 2, SceneId = "d89f74fa-d51e-477a-a425-025a891dd499",
|
|
Tags = RroWith("sports")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Soccer", Description = "Teams of three run around slamming themselves into an over-sized soccer ball. Goal!",
|
|
MaxPlayers = 6, SceneId = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
|
Tags = RroWith("sports")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "DiscGolfLake", Description = "A leisurely stroll through the grass. Throw your disc into the goal. Sounds easy, right?",
|
|
MaxPlayers = 4, SceneId = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
|
Tags = RroWith("sports")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "DiscGolfPropulsion", Description = "Throw your disc through hazards and around wind machines on this challenging course!",
|
|
MaxPlayers = 4, SceneId = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
|
Tags = RroWith("sports")
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Paintball", Description = "Red and Blue teams splat each other in capture the flag and team battle.",
|
|
MaxPlayers = 8, SceneId = "e122fe98-e7db-49e8-a1b1-105424b6e1f0", SubRoomName = "River",
|
|
Tags = RroWith("sports"),
|
|
ExtraSubRooms = new List<DefaultSubRoom>
|
|
{
|
|
new() { Name = "Homestead", SceneId = "a785267d-c579-42ea-be43-fec1992d1ca7" },
|
|
new() { Name = "Quarry", SceneId = "ff4c6427-7079-4f59-b22a-69b089420827" },
|
|
new() { Name = "Clearcut", SceneId = "380d18b5-de9c-49f3-80f7-f4a95c1de161" },
|
|
new() { Name = "Spillway", SceneId = "58763055-2dfb-4814-80b8-16fac5c85709" },
|
|
new() { Name = "Drive-in", SceneId = "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3" }
|
|
}
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "LaserTag", Description = "Teams battle each other and waves of robots.",
|
|
MaxPlayers = 8, SceneId = "239e676c-f12f-489f-bf3a-d4c383d692c3", SubRoomName = "Hangar",
|
|
Tags = RroWith("pvp"),
|
|
ExtraSubRooms = new List<DefaultSubRoom>
|
|
{
|
|
new() { Name = "CyberJunkCity", SceneId = "9d6456ce-6264-48b4-808d-2d96b3d91038" }
|
|
}
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "RecRoyaleSquads", Description = "Squads of three battle it out on Frontier Island. Last squad standing wins!",
|
|
MaxPlayers = 18, SceneId = "253fa009-6e65-4c90-91a1-7137a56a267f", ImageName = "RecRoyaleSquads",
|
|
Tags = RroWith("pvp"), CloningAllowed = false
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "RecRoyaleSolos", Description = "Battle it out on Frontier Island. Last person standing wins!",
|
|
MaxPlayers = 16, SceneId = "b010171f-4875-4e89-baba-61e878cd41e1", ImageName = "RecRoyaleSolos",
|
|
Tags = RroWith("pvp"), CloningAllowed = false, Aliases = new[] { "RecRoyaleSolo" }
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Showdown", Description = "Showdown",
|
|
MaxPlayers = 16, SceneId = SceneLocations.MakerRoom, ImageName = "Showdown",
|
|
Tags = new List<ItemTag>
|
|
{
|
|
new() { Tag = "rro", Type = TagType.AGOnly },
|
|
new() { Tag = "games", Type = TagType.General },
|
|
new() { Tag = "beta", Type = TagType.Auto }
|
|
},
|
|
CloningAllowed = true,
|
|
SaveName = "Showdown.room", BundleName = "Showdown_0_1.assetbundle"
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Lounge", Description = "A low-key lounge to chill with your friends. Great for private parties!",
|
|
MaxPlayers = 20, SceneId = "a067557f-ca32-43e6-b6e5-daaec60b4f5a",
|
|
Tags = Rro(), SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Park", Description = "A sprawling park with amphitheater, play fields, and a cave.",
|
|
MaxPlayers = 20, SceneId = "0a864c86-5a71-4e18-8041-8124e4dc9d98",
|
|
Tags = Rro(), SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "MakerRoom", Description = "This room is a blank canvas. Make it into whatever you like!",
|
|
MaxPlayers = 20, SceneId = "a75f7547-79eb-47c6-8986-6767abcb4f92", ImageName = "MakerRoom",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
|
|
new DefaultRoom
|
|
{
|
|
Name = "Gym", Description = "A school gymnasium for smaller sporting events.",
|
|
MaxPlayers = 20, SceneId = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Stadium", Description = "A professional stadium for larger sporting events.",
|
|
MaxPlayers = 20, SceneId = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "River", Description = "The original outdoor paintball course. Simple, balanced, classic.",
|
|
MaxPlayers = 20, SceneId = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Homestead", Description = "A day on the farm. Great for asymmetrical battles!",
|
|
MaxPlayers = 20, SceneId = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Quarry", Description = "The sun sets on this construction site in the desert. Great for sniping battles!",
|
|
MaxPlayers = 20, SceneId = "ff4c6427-7079-4f59-b22a-69b089420827",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Clearcut", Description = "The sun rises on this logging camp in the forest. Great for mid-range splatting!",
|
|
MaxPlayers = 20, SceneId = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Spillway", Description = "Night shift at the hydroelectric plant. Great for stealthy splatting!",
|
|
MaxPlayers = 20, SceneId = "58763055-2dfb-4814-80b8-16fac5c85709",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Lake", Description = "A leisurely trail to the lake and beyond.",
|
|
MaxPlayers = 20, SceneId = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "PropulsionTestRange", Description = "The science department left some of their equipment laying around. What will you do with it?",
|
|
MaxPlayers = 20, SceneId = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Hangar", Description = "A late-eighties-style laser tag battle arcade.",
|
|
MaxPlayers = 20, SceneId = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "CyberJunkCity", Description = "A late-2080s-style cyberpunk dystopian laser tag battle arcade",
|
|
MaxPlayers = 20, SceneId = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "StuntRunnerBaseRoom", Description = "A soundstage warehouse used to film Stunt Runner and other RecTV shows.",
|
|
MaxPlayers = 20, SceneId = "882e9b96-7115-4b03-86f6-c0c9d8e22e00",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "Registration", Description = "Registration",
|
|
MaxPlayers = 1, SceneId = "cf61556d-68fd-4288-9ae5-7a512621e569",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, CloningAllowed = false
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "ARRoom", Description = "Your real room!",
|
|
MaxPlayers = 4, SceneId = "bf268f5f-b55b-41af-8628-32fa4b5d70b6",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, CloningAllowed = false, SubRoomSandbox = true,
|
|
WarningMask = WarningMask.Custom
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "DriveIn", Description = "A drive-in theater, diner, and truck stop make this a multi-area paint arena",
|
|
MaxPlayers = 20, SceneId = "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised, SubRoomSandbox = true
|
|
},
|
|
|
|
new DefaultRoom
|
|
{
|
|
Name = "RecRally", Description = "A six-player off-road rally race where you boost, bump, and powerslide your way to the finish line!",
|
|
MaxPlayers = 6, SceneId = "56193568-9ae0-498c-8a77-4df79dec91f5",
|
|
Tags = RroWith("racing", "driving", "recrally"),
|
|
WarningMask = WarningMask.IntenseMotion, SupportsTeleportVR = false
|
|
},
|
|
new DefaultRoom
|
|
{
|
|
Name = "FrontierUGC", Description = "A public copyable room featuring the Rec Royale map!",
|
|
MaxPlayers = 40, SceneId = "a16bfd31-ffb9-46ac-a199-362c163130c0", ImageName = "RecRoyale",
|
|
IsRRO = false, Accessibility = Accessibility.Unlised
|
|
}
|
|
};
|
|
|
|
public static async Task EnsureAsync(RecDBContext db)
|
|
{
|
|
try
|
|
{
|
|
await ResetRoomsAsync(db);
|
|
await EnsureCatalogAsync(db);
|
|
await RemoveStrayRroAsync(db);
|
|
await DisableCloningAsync(db);
|
|
await RepairRecCenterAsync(db);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
db.ChangeTracker.Clear();
|
|
}
|
|
}
|
|
|
|
private static async Task RepairRecCenterAsync(RecDBContext db)
|
|
{
|
|
try
|
|
{
|
|
if (await HasRunAsync(db, RecCenterRepairKey, RecCenterRepairVersion)) return;
|
|
|
|
var reference = All.FirstOrDefault(room => Matches(RecCenterRepairRoom, room));
|
|
if (reference == null)
|
|
{
|
|
await MarkRunAsync(db, RecCenterRepairKey, RecCenterRepairVersion);
|
|
return;
|
|
}
|
|
|
|
var rooms = await db.Rooms.ToListAsync();
|
|
var existing = rooms.FirstOrDefault(room => Matches(room.Name, reference));
|
|
if (existing == null)
|
|
{
|
|
await MarkRunAsync(db, RecCenterRepairKey, RecCenterRepairVersion);
|
|
return;
|
|
}
|
|
|
|
var subRooms = await db.SubRooms.ToListAsync();
|
|
var changes = await AlignAsync(db, existing, subRooms, reference);
|
|
|
|
foreach (var sub in subRooms.Where(sub => sub.RoomId == existing.RoomId))
|
|
{
|
|
if (string.IsNullOrEmpty(sub.DataBlob)) continue;
|
|
changes.Add($"subroom {sub.Name} content reset to the stock scene ({sub.DataBlob.Length} chars discarded)");
|
|
sub.DataBlob = string.Empty;
|
|
sub.UnityAssetId = string.Empty;
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
await MarkRunAsync(db, RecCenterRepairKey, RecCenterRepairVersion);
|
|
|
|
}
|
|
catch (Exception)
|
|
{
|
|
db.ChangeTracker.Clear();
|
|
}
|
|
}
|
|
|
|
private static async Task ResetRoomsAsync(RecDBContext db)
|
|
{
|
|
if (await HasRunAsync(db, ShippedRoomResetKey, ShippedRoomResetVersion)) return;
|
|
|
|
var rooms = await db.Rooms.ToListAsync();
|
|
if (rooms.Count == 0)
|
|
{
|
|
await MarkRunAsync(db, ShippedRoomResetKey, ShippedRoomResetVersion);
|
|
return;
|
|
}
|
|
|
|
var ids = rooms.Select(room => room.RoomId).ToList();
|
|
var subRoomIds = await db.SubRooms.Where(sub => ids.Contains(sub.RoomId)).Select(sub => sub.SubRoomId).ToListAsync();
|
|
|
|
db.SubRoomPermissions.RemoveRange(await db.SubRoomPermissions.Where(permission => subRoomIds.Contains(permission.subRoomId)).ToListAsync());
|
|
db.SubRoomHistory.RemoveRange(await db.SubRoomHistory.Where(history => ids.Contains(history.RoomId)).ToListAsync());
|
|
db.SubRooms.RemoveRange(await db.SubRooms.Where(sub => ids.Contains(sub.RoomId)).ToListAsync());
|
|
db.RoomStats.RemoveRange(await db.RoomStats.Where(stats => ids.Contains(stats.RoomId)).ToListAsync());
|
|
db.RoomRoles.RemoveRange(await db.RoomRoles.Where(role => ids.Contains(role.RoomId)).ToListAsync());
|
|
db.RoomKeys.RemoveRange(await db.RoomKeys.Where(key => ids.Contains(key.RoomId)).ToListAsync());
|
|
db.RoomBans.RemoveRange(await db.RoomBans.Where(ban => ids.Contains(ban.roomId)).ToListAsync());
|
|
db.RoomComments.RemoveRange(await db.RoomComments.Where(comment => ids.Contains(comment.RoomId)).ToListAsync());
|
|
db.RoomInteractions.RemoveRange(await db.RoomInteractions.Where(interaction => ids.Contains(interaction.RoomId)).ToListAsync());
|
|
db.RoomInstances.RemoveRange(await db.RoomInstances.Where(instance => ids.Contains(instance.roomId) || subRoomIds.Contains(instance.subRoomId)).ToListAsync());
|
|
db.LeaderboardScores.RemoveRange(await db.LeaderboardScores.Where(score => ids.Contains(score.RoomId)).ToListAsync());
|
|
|
|
var events = await db.PlayerEvents.Where(playerEvent => ids.Contains(playerEvent.RoomId)).ToListAsync();
|
|
if (events.Count > 0)
|
|
{
|
|
var eventIds = events.Select(playerEvent => playerEvent.PlayerEventId).ToList();
|
|
db.PlayerEventResponses.RemoveRange(await db.PlayerEventResponses.Where(response => eventIds.Contains(response.PlayerEventId)).ToListAsync());
|
|
db.PlayerEvents.RemoveRange(events);
|
|
}
|
|
|
|
foreach (var image in await db.Images.Where(image => image.RoomId.HasValue && ids.Contains(image.RoomId.Value)).ToListAsync())
|
|
image.RoomId = null;
|
|
foreach (var message in await db.Messages.Where(message => message.RoomId.HasValue && ids.Contains(message.RoomId.Value)).ToListAsync())
|
|
message.RoomId = null;
|
|
|
|
db.Rooms.RemoveRange(rooms);
|
|
await db.SaveChangesAsync();
|
|
|
|
await db.AddDormRoom();
|
|
await ClearCatalogMarkerAsync(db);
|
|
await MarkRunAsync(db, ShippedRoomResetKey, ShippedRoomResetVersion);
|
|
|
|
var dorm = await db.Rooms.FirstOrDefaultAsync(room => room.IsDorm && room.CreatorAccountId == 1);
|
|
}
|
|
|
|
private static async Task ClearCatalogMarkerAsync(RecDBContext db)
|
|
{
|
|
var row = await db.GameConfig.FirstOrDefaultAsync(config => config.Key == AlignmentKey);
|
|
if (row == null)
|
|
await db.GameConfig.AddAsync(new ConfigVal(AlignmentKey, "0"));
|
|
else
|
|
row.Value = "0";
|
|
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
private static async Task EnsureCatalogAsync(RecDBContext db)
|
|
{
|
|
try
|
|
{
|
|
if (await IsAlignedAsync(db)) return;
|
|
|
|
var rooms = await db.Rooms.ToListAsync();
|
|
var subRooms = await db.SubRooms.ToListAsync();
|
|
var statsRoomIds = await db.RoomStats.Select(x => x.RoomId).ToListAsync();
|
|
|
|
var created = new List<string>();
|
|
var aligned = new List<string>();
|
|
|
|
foreach (var reference in All)
|
|
{
|
|
var existing = rooms.FirstOrDefault(room => Matches(room.Name, reference));
|
|
if (existing == null)
|
|
{
|
|
var room = await CreateAsync(db, reference, statsRoomIds);
|
|
rooms.Add(room);
|
|
created.Add($"{reference.Name} ({reference.MaxPlayers} players)");
|
|
continue;
|
|
}
|
|
|
|
var changes = await AlignAsync(db, existing, subRooms, reference);
|
|
if (changes.Count > 0)
|
|
aligned.Add($"{existing.Name}: {string.Join(", ", changes)}");
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
await MarkAlignedAsync(db);
|
|
|
|
foreach (var line in created) ; foreach (var line in aligned) ; }
|
|
catch (Exception)
|
|
{
|
|
db.ChangeTracker.Clear();
|
|
}
|
|
}
|
|
|
|
private static async Task RemoveStrayRroAsync(RecDBContext db)
|
|
{
|
|
if (await HasRunAsync(db, RroCorrectionKey, RroCorrectionVersion)) return;
|
|
|
|
var rooms = await db.Rooms.ToListAsync();
|
|
var corrected = new List<string>();
|
|
|
|
foreach (var room in rooms)
|
|
{
|
|
var entry = All.FirstOrDefault(candidate => !candidate.IsRRO && Matches(room.Name, candidate));
|
|
if (entry == null) continue;
|
|
|
|
var tags = StringTools.ReadTags(room.Tags);
|
|
var strayTags = tags.Where(tag => string.Equals(tag.Tag, "rro", StringComparison.OrdinalIgnoreCase)).ToList();
|
|
if (strayTags.Count == 0 && !room.IsRRO) continue;
|
|
|
|
foreach (var tag in strayTags)
|
|
tags.Remove(tag);
|
|
|
|
room.Tags = StringTools.CreateTagString(tags);
|
|
room.IsRRO = false;
|
|
corrected.Add(room.Name);
|
|
}
|
|
|
|
if (corrected.Count > 0)
|
|
await db.SaveChangesAsync();
|
|
await MarkRunAsync(db, RroCorrectionKey, RroCorrectionVersion);
|
|
}
|
|
|
|
private static async Task DisableCloningAsync(RecDBContext db)
|
|
{
|
|
if (await HasRunAsync(db, CloningCorrectionKey, CloningCorrectionVersion)) return;
|
|
|
|
var rooms = await db.Rooms.ToListAsync();
|
|
var off = new List<string>();
|
|
var on = new List<string>();
|
|
|
|
foreach (var room in rooms)
|
|
{
|
|
var entry = All.FirstOrDefault(candidate => Matches(room.Name, candidate));
|
|
if (entry == null || entry.CloningAllowed == room.CloningAllowed) continue;
|
|
|
|
room.CloningAllowed = entry.CloningAllowed;
|
|
(entry.CloningAllowed ? on : off).Add(room.Name);
|
|
}
|
|
|
|
if (off.Count > 0 || on.Count > 0)
|
|
await db.SaveChangesAsync();
|
|
await MarkRunAsync(db, CloningCorrectionKey, CloningCorrectionVersion);
|
|
|
|
if (off.Count == 0 && on.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
private static async Task<RoomData> CreateAsync(RecDBContext db, DefaultRoom reference, List<long> statsRoomIds)
|
|
{
|
|
var room = new RoomData
|
|
{
|
|
Name = reference.Name,
|
|
Description = reference.Description,
|
|
ImageName = reference.ImageName ?? reference.Name,
|
|
MaxPlayers = reference.MaxPlayers,
|
|
MaxPlayerCalculationMode = MaxPlayerMode.OnlyEntrySubrooms,
|
|
CreatorAccountId = 1,
|
|
IsRRO = reference.IsRRO,
|
|
IsDorm = false,
|
|
Accessibility = reference.Accessibility,
|
|
CreatedAt = DateTime.Now,
|
|
State = RoomState.Active,
|
|
CloningAllowed = reference.CloningAllowed,
|
|
DisableRoomComments = reference.DisableRoomComments,
|
|
WarningMask = reference.WarningMask,
|
|
SupportsTeleportVR = reference.SupportsTeleportVR,
|
|
Tags = StringTools.CreateTagString(reference.Tags.ToList())
|
|
};
|
|
|
|
await db.Rooms.AddAsync(room);
|
|
await db.SaveChangesAsync();
|
|
|
|
await AddSubRoomAsync(db, room.RoomId, reference.SubRoomName, reference.SceneId, reference.MaxPlayers,
|
|
reference.Accessibility, reference.SubRoomSandbox, reference.SaveName, reference.BundleName);
|
|
foreach (var extra in reference.ExtraSubRooms)
|
|
await AddSubRoomAsync(db, room.RoomId, extra.Name, extra.SceneId, ExtraSubRoomSize(extra, reference),
|
|
reference.Accessibility, reference.SubRoomSandbox);
|
|
|
|
if (!statsRoomIds.Contains(room.RoomId))
|
|
{
|
|
await db.RoomStats.AddAsync(new RoomStats { RoomId = room.RoomId });
|
|
statsRoomIds.Add(room.RoomId);
|
|
}
|
|
|
|
await db.RoomRoles.AddAsync(new RoomRole
|
|
{
|
|
RoomId = room.RoomId,
|
|
AccountId = 1,
|
|
Role = RoleType.Creator,
|
|
InvitedRole = RoleType.None
|
|
});
|
|
|
|
await db.SaveChangesAsync();
|
|
return room;
|
|
}
|
|
|
|
private static async Task<List<string>> AlignAsync(RecDBContext db, RoomData room, List<SubRoom> subRooms, DefaultRoom reference)
|
|
{
|
|
var changes = new List<string>();
|
|
var image = reference.ImageName ?? reference.Name;
|
|
|
|
if (room.MaxPlayers != reference.MaxPlayers)
|
|
{
|
|
changes.Add($"max players {room.MaxPlayers} -> {reference.MaxPlayers}");
|
|
room.MaxPlayers = reference.MaxPlayers;
|
|
}
|
|
if (!string.Equals(room.ImageName, image, StringComparison.Ordinal))
|
|
{
|
|
changes.Add($"image '{room.ImageName}' -> '{image}'");
|
|
room.ImageName = image;
|
|
}
|
|
if (room.Accessibility != reference.Accessibility)
|
|
{
|
|
changes.Add($"accessibility {room.Accessibility} -> {reference.Accessibility}");
|
|
room.Accessibility = reference.Accessibility;
|
|
}
|
|
if (room.CloningAllowed != reference.CloningAllowed)
|
|
{
|
|
changes.Add($"cloning {room.CloningAllowed} -> {reference.CloningAllowed}");
|
|
room.CloningAllowed = reference.CloningAllowed;
|
|
}
|
|
if (room.DisableRoomComments != reference.DisableRoomComments)
|
|
{
|
|
changes.Add($"room comments {(reference.DisableRoomComments ? "off" : "on")}");
|
|
room.DisableRoomComments = reference.DisableRoomComments;
|
|
}
|
|
if (room.WarningMask != reference.WarningMask)
|
|
{
|
|
changes.Add($"warning mask {room.WarningMask} -> {reference.WarningMask}");
|
|
room.WarningMask = reference.WarningMask;
|
|
}
|
|
if (room.SupportsTeleportVR != reference.SupportsTeleportVR)
|
|
{
|
|
changes.Add($"teleport VR {reference.SupportsTeleportVR}");
|
|
room.SupportsTeleportVR = reference.SupportsTeleportVR;
|
|
}
|
|
if (room.IsRRO != reference.IsRRO)
|
|
{
|
|
changes.Add(reference.IsRRO ? "marked IsRRO" : "no longer marked IsRRO");
|
|
room.IsRRO = reference.IsRRO;
|
|
}
|
|
|
|
var mine = subRooms.Where(sub => sub.RoomId == room.RoomId).OrderBy(sub => sub.SubRoomId).ToList();
|
|
var entry = mine.FirstOrDefault();
|
|
if (entry == null)
|
|
{
|
|
subRooms.Add(await AddSubRoomAsync(db, room.RoomId, reference.SubRoomName, reference.SceneId,
|
|
reference.MaxPlayers, reference.Accessibility, reference.SubRoomSandbox, reference.SaveName,
|
|
reference.BundleName));
|
|
changes.Add($"added the {reference.SubRoomName} subroom");
|
|
}
|
|
else
|
|
{
|
|
AlignSubRoom(changes, entry, reference.SubRoomName, reference.SceneId, reference.MaxPlayers,
|
|
reference.Accessibility, reference.SubRoomSandbox);
|
|
}
|
|
|
|
foreach (var extra in reference.ExtraSubRooms)
|
|
{
|
|
var match = subRooms.FirstOrDefault(sub => sub.RoomId == room.RoomId
|
|
&& string.Equals(sub.UnitySceneId, extra.SceneId, StringComparison.OrdinalIgnoreCase));
|
|
if (match == null)
|
|
{
|
|
subRooms.Add(await AddSubRoomAsync(db, room.RoomId, extra.Name, extra.SceneId,
|
|
ExtraSubRoomSize(extra, reference), reference.Accessibility, reference.SubRoomSandbox));
|
|
changes.Add($"added the {extra.Name} subroom");
|
|
}
|
|
else
|
|
{
|
|
AlignSubRoom(changes, match, extra.Name, extra.SceneId, ExtraSubRoomSize(extra, reference),
|
|
reference.Accessibility, reference.SubRoomSandbox);
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
private static void AlignSubRoom(List<string> changes, SubRoom sub, string name, string sceneId, int maxPlayers, Accessibility accessibility, bool sandbox, string? saveName = null, string? bundleName = null)
|
|
{
|
|
var what = $"subroom {sub.Name}";
|
|
|
|
if (!string.Equals(sub.Name, name, StringComparison.Ordinal))
|
|
{
|
|
changes.Add($"{what} renamed to {name}");
|
|
sub.Name = name;
|
|
}
|
|
if (!string.Equals(sub.UnitySceneId, sceneId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
changes.Add($"{what} scene {sub.UnitySceneId} -> {sceneId}");
|
|
sub.UnitySceneId = sceneId;
|
|
}
|
|
if (sub.MaxPlayers != maxPlayers)
|
|
{
|
|
changes.Add($"{what} max players {sub.MaxPlayers} -> {maxPlayers}");
|
|
sub.MaxPlayers = maxPlayers;
|
|
}
|
|
if (sub.Accessibility != accessibility)
|
|
{
|
|
changes.Add($"{what} accessibility {sub.Accessibility} -> {accessibility}");
|
|
sub.Accessibility = accessibility;
|
|
}
|
|
if (sub.IsSandbox != sandbox)
|
|
{
|
|
changes.Add($"{what} sandbox {sandbox}");
|
|
sub.IsSandbox = sandbox;
|
|
}
|
|
if (saveName != null && !string.Equals(sub.DataBlob, saveName, StringComparison.Ordinal))
|
|
{
|
|
changes.Add($"{what} room file '{(sub.DataBlob.Length == 0 ? "none" : sub.DataBlob)}' -> '{saveName}'");
|
|
sub.DataBlob = saveName;
|
|
}
|
|
if (bundleName != null && !string.Equals(sub.UnityAssetId, bundleName, StringComparison.Ordinal))
|
|
{
|
|
changes.Add($"{what} bundle '{(sub.UnityAssetId.Length == 0 ? "none" : sub.UnityAssetId)}' -> '{bundleName}'");
|
|
sub.UnityAssetId = bundleName;
|
|
}
|
|
}
|
|
|
|
private static async Task<SubRoom> AddSubRoomAsync(RecDBContext db, long roomId, string name, string sceneId, int maxPlayers, Accessibility accessibility, bool sandbox, string? saveName = null, string? bundleName = null)
|
|
{
|
|
var subRoom = new SubRoom
|
|
{
|
|
RoomId = roomId,
|
|
SavedByAccountId = 1,
|
|
Name = name,
|
|
DataBlob = saveName ?? "",
|
|
UnityAssetId = bundleName ?? "",
|
|
IsSandbox = sandbox,
|
|
MaxPlayers = maxPlayers,
|
|
Accessibility = accessibility,
|
|
UnitySceneId = sceneId,
|
|
CreatedAt = DateTime.Now
|
|
};
|
|
|
|
await db.SubRooms.AddAsync(subRoom);
|
|
return subRoom;
|
|
}
|
|
|
|
private static int ExtraSubRoomSize(DefaultSubRoom subRoom, DefaultRoom room) =>
|
|
subRoom.MaxPlayers > 0 ? subRoom.MaxPlayers : room.MaxPlayers;
|
|
|
|
|
|
private static Task<bool> IsAlignedAsync(RecDBContext db) => HasRunAsync(db, AlignmentKey, AlignmentVersion);
|
|
|
|
private static Task MarkAlignedAsync(RecDBContext db) => MarkRunAsync(db, AlignmentKey, AlignmentVersion);
|
|
|
|
private static async Task<bool> HasRunAsync(RecDBContext db, string key, int version)
|
|
{
|
|
var row = await db.GameConfig.FirstOrDefaultAsync(x => x.Key == key);
|
|
return int.TryParse(row?.Value, out var recorded) && recorded >= version;
|
|
}
|
|
|
|
private static async Task MarkRunAsync(RecDBContext db, string key, int version)
|
|
{
|
|
var row = await db.GameConfig.FirstOrDefaultAsync(x => x.Key == key);
|
|
if (row == null)
|
|
await db.GameConfig.AddAsync(new ConfigVal(key, version.ToString()));
|
|
else
|
|
row.Value = version.ToString();
|
|
|
|
await db.SaveChangesAsync();
|
|
}
|
|
|
|
private static bool Matches(string? existingName, DefaultRoom reference)
|
|
{
|
|
var key = NormalizeName(existingName);
|
|
if (key.Length == 0) return false;
|
|
|
|
return key == NormalizeName(reference.Name)
|
|
|| reference.Aliases.Any(alias => key == NormalizeName(alias));
|
|
}
|
|
|
|
private static string NormalizeName(string? name) =>
|
|
string.Concat((name ?? "").Where(char.IsLetterOrDigit)).ToLowerInvariant();
|
|
|
|
private static List<ItemTag> Rro() => new() { new ItemTag { Tag = "rro", Type = TagType.AGOnly } };
|
|
|
|
private static List<ItemTag> RroWith(params string[] tags)
|
|
{
|
|
var list = Rro();
|
|
list.AddRange(tags.Select(tag => new ItemTag { Tag = tag, Type = TagType.General }));
|
|
return list;
|
|
}
|
|
}
|
|
|
|
public sealed class DefaultRoom
|
|
{
|
|
public string Name { get; init; } = "";
|
|
public string Description { get; init; } = "";
|
|
|
|
public int MaxPlayers { get; init; }
|
|
|
|
public string SceneId { get; init; } = "";
|
|
|
|
public string SubRoomName { get; init; } = "Home";
|
|
|
|
public string? ImageName { get; init; }
|
|
|
|
public string? SaveName { get; init; }
|
|
|
|
public string? BundleName { get; init; }
|
|
|
|
public IReadOnlyList<ItemTag> Tags { get; init; } = Array.Empty<ItemTag>();
|
|
|
|
public IReadOnlyList<DefaultSubRoom> ExtraSubRooms { get; init; } = Array.Empty<DefaultSubRoom>();
|
|
|
|
public IReadOnlyList<string> Aliases { get; init; } = Array.Empty<string>();
|
|
|
|
public Accessibility Accessibility { get; init; } = Accessibility.Public;
|
|
|
|
public bool CloningAllowed
|
|
{
|
|
get => cloningAllowedOverride ?? Accessibility == Accessibility.Unlised;
|
|
init => cloningAllowedOverride = value;
|
|
}
|
|
|
|
private bool? cloningAllowedOverride;
|
|
public bool IsRRO { get; init; } = true;
|
|
|
|
public bool DisableRoomComments { get; init; }
|
|
public bool SubRoomSandbox { get; init; }
|
|
public WarningMask WarningMask { get; init; } = WarningMask.None;
|
|
public bool SupportsTeleportVR { get; init; } = true;
|
|
}
|
|
|
|
public sealed class DefaultSubRoom
|
|
{
|
|
public string Name { get; init; } = "";
|
|
public string SceneId { get; init; } = "";
|
|
|
|
public int MaxPlayers { get; init; }
|
|
}
|
|
}
|