Files
skidfire fe3468c65d yikes,
2026-09-06 19:59:27 -04:00

1392 lines
48 KiB
C#

using LiteDB;
using Vanadium.Classes;
using Vanadium.Classes.DBs.DBClasses;
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using static Vanadium.Classes.DBs.DBClasses.PhotonAccessTokenDBClasses;
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
using static Vanadium.Classes.DBs.DBClasses.RoomDBClasses;
namespace Vanadium.Classes.DBs
{
public class RoomDB
{
public static LiteDatabase RoomDBFile = new LiteDatabase("Filename=" + Path.Join(Program.dataDir, "DBs", "Rooms.db") + ";Connection=shared");
public static readonly ILiteCollection<Room> Rooms = RoomDBFile.GetCollection<Room>("Rooms");
public static readonly ILiteCollection<currentSave> SubRoomSaves = RoomDBFile.GetCollection<currentSave>("SubRoomSaves");
public static readonly ILiteCollection<DbCounters> Counters = RoomDBFile.GetCollection<DbCounters>("Counters");
public static void Setup()
{
Rooms.EnsureIndex(x => x.RoomId);
Rooms.EnsureIndex(x => x.CreatorAccountId);
Rooms.EnsureIndex(x => x.Name);
Rooms.EnsureIndex("TagIndex", "$.Tags[*].Tag");
Counters.EnsureIndex(x => x.Key);
SubRoomSaves.EnsureIndex(x => x.SubRoomDataSaveId);
SubRoomSaves.EnsureIndex(x => x.RoomId);
SubRoomSaves.EnsureIndex(x => x.SubRoomId);
}
public static async Task ImportRooms(string path)
{
if (!File.Exists(path))
{
Console.WriteLine($"[DB] No import file found at {path}, skipping room import.");
return;
}
try
{
string json = await File.ReadAllTextAsync(path);
var importedRooms = System.Text.Json.JsonSerializer.Deserialize<List<Room>>(json);
if (importedRooms != null)
{
foreach (var room in importedRooms)
await AddRoom(room, log: true, shouldAssignNewIds: true);
Console.WriteLine($"[DB] Imported {importedRooms.Count} rooms from {path}.");
}
}
catch (Exception ex)
{
Console.WriteLine($"[DB Error] Failed to import rooms from {path}: {ex.Message}");
}
}
public static Room? SetRoomSubroomRoomFileAgain(
long roomId,
long subRoomId,
string roomFile,
string roomDesc,
long playerId,
int platform = 0,
int deviceClass = 0)
{
var room = Rooms.FindById(roomId);
if (room == null)
return null;
var subRoom = room.SubRooms?.FirstOrDefault(s => s.SubRoomId == subRoomId);
if (subRoom == null)
return room;
subRoom.DataBlob = roomFile;
subRoom.SavedByAccountId = playerId;
var existingUnityAssetId = !string.IsNullOrEmpty(subRoom.CurrentSave?.UnityAssetId)
? subRoom.CurrentSave.UnityAssetId
: null;
var save = new currentSave
{
SubRoomDataSaveId = GetNextSubRoomSaveId(),
RoomId = roomId,
SubRoomId = subRoomId,
DataBlob = roomFile,
Description = roomDesc ?? "",
SavedByAccountId = playerId,
SavedOnPlatform = platform,
SavedOnDeviceClass = deviceClass,
UnityAssetId = existingUnityAssetId,
CreatedAt = DateTime.UtcNow
};
SubRoomSaves.Insert(save);
Rooms.Update(room);
return room;
}
public static void ExportRooms()
{
var allRooms = Rooms.FindAll().ToList();
var saves = SubRoomSaves.FindAll().ToList();
foreach (var room in allRooms)
{
foreach (var sub in room.SubRooms ?? new List<SubRooms>())
{
}
}
string outputPath = Path.Combine(Program.dataDir, "exported_rooms.json");
File.WriteAllText(outputPath, System.Text.Json.JsonSerializer.Serialize(allRooms));
Console.WriteLine($"[DB] Exported {allRooms.Count} rooms → {outputPath}");
}
public static List<Room> GetRoomsByIds(List<long> ids)
{
if (ids == null || ids.Count == 0) return new List<Room>();
return Rooms.Find(room => ids.Contains(room.RoomId)).ToList();
}
public static async Task ClearRooms(bool log = false)
{
await Task.Run(() =>
{
try
{
var dormRooms = Rooms.Find(r => r.IsDorm).ToList();
Rooms.DeleteAll();
foreach (var dorm in dormRooms)
Rooms.Insert(dorm);
if (log)
Console.WriteLine($"[DB] Cleared rooms, preserved {dormRooms.Count} dorm rooms.");
}
catch (Exception ex)
{
Console.WriteLine($"[DB Error] Failed to clear rooms: {ex.Message}");
}
});
}
public static async Task AddRoom(Room newRoom, bool log = false, bool shouldAssignNewIds = true)
{
if (newRoom == null) return;
await Task.Run(() =>
{
try
{
if (shouldAssignNewIds || newRoom.RoomId <= 0)
newRoom.RoomId = GetNextRoomId();
newRoom.RankedEntityId = newRoom.RoomId.ToString();
if (newRoom.Stats == null)
newRoom.Stats = new Stats();
if (newRoom.LoadScreens == null)
newRoom.LoadScreens = new List<LoadScreens>();
if (newRoom.SubRooms != null && newRoom.SubRooms.Any())
{
foreach (var sub in newRoom.SubRooms)
{
if (shouldAssignNewIds || sub.SubRoomId <= 0)
sub.SubRoomId = GetNextSubRoomId();
sub.RoomId = newRoom.RoomId;
}
}
Rooms.Insert(newRoom);
if (log)
Console.WriteLine($"[DB] Added room: {newRoom.Name} (ID: {newRoom.RoomId})");
}
catch (Exception ex)
{
Console.WriteLine($"[DB Error] Failed to add room {newRoom.Name}: {ex.Message}");
}
});
}
public static Room? MoveSubRoomToRoom(
long fromRoomId,
long subRoomId,
long targetRoomId,
long playerId)
{
var sourceRoom = Rooms.FindById(fromRoomId);
var targetRoom = Rooms.FindById(targetRoomId);
if (sourceRoom == null || targetRoom == null)
return null;
var subRoom = sourceRoom.SubRooms?
.FirstOrDefault(s => s.SubRoomId == subRoomId);
if (subRoom == null)
return null;
bool canEditSource = UserCanEditRoom(fromRoomId, playerId);
bool canEditTarget = UserCanEditRoom(targetRoomId, playerId);
if (!canEditSource || !canEditTarget)
return null;
targetRoom.SubRooms ??= new List<SubRooms>();
long oldSubRoomId = subRoom.SubRoomId;
long newSubRoomId = GetNextSubRoomId();
var json = System.Text.Json.JsonSerializer.Serialize(subRoom);
var movedSubRoom = System.Text.Json.JsonSerializer.Deserialize<SubRooms>(json);
if (movedSubRoom == null)
return null;
movedSubRoom.SubRoomId = newSubRoomId;
movedSubRoom.RoomId = targetRoomId;
targetRoom.SubRooms.Add(movedSubRoom);
var saves = SubRoomSaves.Find(s =>
s.RoomId == fromRoomId &&
s.SubRoomId == oldSubRoomId).ToList();
foreach (var save in saves)
{
save.RoomId = targetRoomId;
save.SubRoomId = newSubRoomId;
SubRoomSaves.Update(save);
}
sourceRoom.SubRooms.Remove(subRoom);
Rooms.Update(sourceRoom);
Rooms.Update(targetRoom);
return targetRoom;
}
public static long AddRoomSync(Room newRoom, bool shouldAssignNewIds = true)
{
if (newRoom == null) return 0;
if (shouldAssignNewIds || newRoom.RoomId <= 0)
newRoom.RoomId = GetNextRoomId();
newRoom.RankedEntityId = newRoom.RoomId.ToString();
if (newRoom.Stats == null) newRoom.Stats = new Stats();
if (newRoom.LoadScreens == null) newRoom.LoadScreens = new List<LoadScreens>();
if (newRoom.SubRooms != null && newRoom.SubRooms.Any())
{
foreach (var sub in newRoom.SubRooms)
{
if (shouldAssignNewIds || sub.SubRoomId <= 0)
sub.SubRoomId = GetNextSubRoomId();
sub.RoomId = newRoom.RoomId;
}
}
Rooms.Insert(newRoom);
Console.WriteLine($"[DB] Added room: {newRoom.Name} (ID: {newRoom.RoomId})");
return newRoom.RoomId;
}
public static long GetNextRoomId()
{
long id;
do
{
id = Random.Shared.NextInt64(100_000_000_000L, long.MaxValue);
} while (Rooms.FindById(id) != null);
return id;
}
public static long GetNextSubRoomId()
{
long id;
do
{
id = Random.Shared.NextInt64(100_000_000_000L, long.MaxValue);
} while (Rooms.FindAll().Any(r => r.SubRooms != null && r.SubRooms.Any(s => s.SubRoomId == id)));
return id;
}
public static long GetNextSubRoomSaveId()
{
const long saveIdCeiling = int.MaxValue / 16;
long id;
do
{
id = Random.Shared.NextInt64(1, saveIdCeiling);
} while (SubRoomSaves.FindOne(s => s.SubRoomDataSaveId == id) != null);
return id;
}
public static Room? GetRoom(long roomId)
{
var col = RoomDBFile.GetCollection("Rooms");
var doc = col.FindById(new BsonValue(roomId));
if (doc == null) return null;
try
{
return BsonMapper.Global.Deserialize<Room>(doc);
}
catch (Exception ex)
{
Console.WriteLine($"[DB] Skipped corrupt room doc {roomId}: {ex.Message}");
return null;
}
}
public static Room GetRoomByName(string name)
=> Rooms.FindOne(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
public static List<Room> GetRoomsByNames(List<string> names)
{
if (names == null || names.Count == 0) return new List<Room>();
return Rooms.Find(room => names.Contains(room.Name, StringComparer.OrdinalIgnoreCase)).ToList();
}
public static List<Room> GetRoomsByCreator(long playerId)
{
var col = RoomDBFile.GetCollection("Rooms");
var results = new List<Room>();
foreach (var doc in col.FindAll())
{
try
{
var room = BsonMapper.Global.Deserialize<Room>(doc);
if (room == null)
continue;
if (room.Accessibility != RoomAccessibility.Public)
continue;
bool isCreator = room.CreatorAccountId == playerId;
bool hasOwnerRole = room.Roles?.Any(r =>
r.AccountId == playerId &&
(r.Role == Role.Creator ||
r.Role == Role.Host ||
r.Role == Role.CoOwner ||
r.Role == Role.TemporaryCoOwner)
) ?? false;
if (!isCreator && !hasOwnerRole)
continue;
results.Add(room);
}
catch (Exception ex)
{
Console.WriteLine($"[oh shittinhs] Skipped corrupt room doc {doc["_id"]}: {ex.Message}");
}
}
return results;
}
public static long GetAllRoomsCount() => Rooms.Count();
public static bool DoesRoomExist(long roomId) => Rooms.FindById(new BsonValue(roomId)) != null;
public static (List<Room> Results, int Total) GetHotRooms(string? tag, int skip, int take, bool risingSort = false)
{
var col = RoomDBFile.GetCollection("Rooms");
string t = tag?.ToLowerInvariant() ?? "";
bool hasTag = !string.IsNullOrWhiteSpace(t);
bool isRRO = (t == "rro" || t == "recroomoriginal");
var results = new List<Room>();
foreach (var doc in col.FindAll())
{
try
{
var room = BsonMapper.Global.Deserialize<Room>(doc);
if (room == null) continue;
if (room.IsDorm) continue;
if (room.Accessibility != RoomAccessibility.Public) continue;
if (!hasTag)
{
bool isRoomRRO = room.Tags != null &&
room.Tags.Any(x =>
x.Tag.Equals("rro", StringComparison.OrdinalIgnoreCase) ||
x.Tag.Equals("recroomoriginal", StringComparison.OrdinalIgnoreCase));
if (isRoomRRO) continue;
}
else
{
if (isRRO)
{
bool hasRRO = room.Tags != null &&
room.Tags.Any(x =>
x.Tag.Equals("rro", StringComparison.OrdinalIgnoreCase) ||
x.Tag.Equals("recroomoriginal", StringComparison.OrdinalIgnoreCase));
if (!hasRRO) continue;
}
}
results.Add(room);
}
catch
{
}
}
IEnumerable<Room> ordered;
if (risingSort)
{
ordered = results
.Where(r => r.Stats?.CheerCount >= 5)
.OrderByDescending(r => r.CreatedAt);
}
else if (t == "new")
{
ordered = results.OrderByDescending(r => r.CreatedAt);
}
else if (!hasTag)
{
ordered = results.OrderByDescending(r => r.Stats?.CheerCount ?? 0);
}
else
{
ordered = results.OrderByDescending(r => r.Stats?.VisitCount ?? 0);
}
var finalResults = ordered.Skip(skip).Take(take).ToList();
return (finalResults, results.Count);
}
public static List<Room> GetBaseRooms()
{
var col = RoomDBFile.GetCollection("Rooms");
var results = new List<Room>();
foreach (var doc in col.FindAll())
{
try
{
var room = BsonMapper.Global.Deserialize<Room>(doc);
if (room == null)
continue;
if (!room.IsDorm && room.Tags != null && room.Tags.Any(t => t.Tag == "base"))
results.Add(room);
}
catch (Exception ex)
{
Console.WriteLine($"[DB] Skipped corrupt room doc {doc["_id"]}: {ex.Message}");
}
}
return results;
}
public static (List<Room> Results, int Total) Search(string query, bool isBase = false, int skip = 0, int take = 30)
{
if (string.IsNullOrWhiteSpace(query))
return (new List<Room>(), 0);
var segments = query.Split('|', StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => !string.IsNullOrWhiteSpace(s))
.ToList();
var candidatePool = Rooms.FindAll()
.Where(r => !r.IsDorm)
.Where(r => !r.ExcludeFromSearch)
.Where(r => isBase || r.Accessibility == RoomAccessibility.Public)
.ToList();
var allPlayers = PlayerDB.Players.FindAll().ToList();
var matched = new Dictionary<long, Room>();
foreach (var segment in segments)
{
var terms = segment.Split(new char[] { ' ', '+' }, StringSplitOptions.RemoveEmptyEntries);
var nameParts = new List<string>();
var tagParts = new List<string>();
var authorParts = new List<string>();
foreach (var term in terms)
{
if (term.StartsWith("#"))
tagParts.Add(term.Substring(1).ToLowerInvariant().Trim());
else if (term.StartsWith("@"))
authorParts.Add(term.Substring(1).ToLowerInvariant().Trim());
else
nameParts.Add(term.ToLowerInvariant().Trim());
}
long? resolvedAuthorId = null;
if (authorParts.Count > 0)
{
var authorQuery = string.Join(" ", authorParts);
var exactMatch = allPlayers.FirstOrDefault(p =>
string.Equals(p.Player?.Username, authorQuery, StringComparison.OrdinalIgnoreCase) ||
string.Equals(p.Player?.DisplayName, authorQuery, StringComparison.OrdinalIgnoreCase));
if (exactMatch != null)
{
resolvedAuthorId = exactMatch.PlayerId;
}
else
{
var fuzzyMatch = allPlayers
.Where(p => p.Player != null)
.Select(p => new
{
Player = p,
Score = Math.Max(
ScoreShit((p.Player!.Username ?? "").ToLowerInvariant(), authorQuery),
ScoreShit((p.Player!.DisplayName ?? "").ToLowerInvariant(), authorQuery))
})
.Where(x => x.Score > 0)
.OrderByDescending(x => x.Score)
.FirstOrDefault();
if (fuzzyMatch != null)
resolvedAuthorId = fuzzyMatch.Player.PlayerId;
else
continue;
}
}
foreach (var room in candidatePool)
{
if (resolvedAuthorId.HasValue && room.CreatorAccountId != resolvedAuthorId.Value)
continue;
if (tagParts.Count > 0)
{
var roomTags = (room.Tags ?? new List<Tags>())
.Select(t => t.Tag.ToLowerInvariant().Trim())
.ToList();
bool allTagsMatch = tagParts.All(tp =>
{
if (roomTags.Contains(tp))
return true;
foreach (var rt in roomTags)
{
if (rt.Contains(tp) || tp.Contains(rt))
return true;
int dist = EditDistance(rt, tp);
int maxLen = Math.Max(rt.Length, tp.Length);
if (maxLen > 0 && (double)dist / maxLen <= 0.34)
return true;
}
return false;
});
if (!allTagsMatch) continue;
}
if (nameParts.Count > 0)
{
string roomNameLower = (room.Name ?? "").ToLowerInvariant();
bool nameMatch = nameParts.All(np =>
{
if (roomNameLower.Contains(np))
return true;
int dist = EditDistance(roomNameLower, np);
int maxLen = Math.Max(roomNameLower.Length, np.Length);
if (maxLen > 0 && (double)dist / maxLen <= 0.34)
return true;
return false;
});
if (!nameMatch) continue;
}
matched[room.RoomId] = room;
}
}
var all = matched.Values
.OrderByDescending(r => r.Stats?.CheerCount ?? 0)
.ThenByDescending(r => r.Stats?.VisitCount ?? 0)
.ToList();
return (all.Skip(skip).Take(take).ToList(), all.Count);
}
private static int ScoreShit(string source, string target)
{
if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(target)) return 0;
if (source == target) return 1000;
if (source.Contains(target) || target.Contains(source)) return 500;
int dist = EditDistance(source, target);
int maxLen = Math.Max(source.Length, target.Length);
if (maxLen == 0) return 0;
double similarity = 1.0 - (double)dist / maxLen;
if (similarity >= 0.66) return (int)(similarity * 100);
return 0;
}
private static int EditDistance(string a, string b)
{
if (string.IsNullOrEmpty(a)) return b?.Length ?? 0;
if (string.IsNullOrEmpty(b)) return a.Length;
int m = a.Length, n = b.Length;
var dp = new int[m + 1, n + 1];
for (int i = 0; i <= m; i++) dp[i, 0] = i;
for (int j = 0; j <= n; j++) dp[0, j] = j;
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
dp[i, j] = a[i - 1] == b[j - 1]
? dp[i - 1, j - 1]
: 1 + Math.Min(dp[i - 1, j - 1], Math.Min(dp[i - 1, j], dp[i, j - 1]));
}
}
return dp[m, n];
}
public static Room? SetRoomName(long roomId, string roomName)
{
var existing = Rooms.FindOne(x => x.Name == roomName && x.RoomId != roomId);
if (existing != null) return null;
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.Name = roomName;
Rooms.Update(room);
return room;
}
public static Room? SetRoomDescription(long roomId, string description)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.Description = description;
Rooms.Update(room);
return room;
}
public static Room? SetRoomImageName(long roomId, string imageName)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.ImageName = imageName;
Rooms.Update(room);
return room;
}
public static Room? SetRoomAccessibility(long roomId, RoomAccessibility accessibility)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.Accessibility = accessibility;
Rooms.Update(room);
return room;
}
public static Room? SetRoomCloning(long roomId, bool cloningEnabled)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.CloningAllowed = cloningEnabled;
Rooms.Update(room);
return room;
}
public static Room? SetRoomRestrictions(long roomId, bool supportsScreens, bool supportsWalkVR, bool supportsTeleportVR, bool supportsJuniors)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.SupportsScreens = supportsScreens;
room.SupportsWalkVR = supportsWalkVR;
room.SupportsTeleportVR = supportsTeleportVR;
room.SupportsJuniors = supportsJuniors;
Rooms.Update(room);
return room;
}
public static Room? SetRoomLoadscreen(long roomId, string? imageName, string? title, string? subtitle)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.LoadScreens = new List<LoadScreens>
{
new LoadScreens
{
ImageName = imageName ?? "",
Title = title ?? "",
Subtitle = subtitle ?? ""
}
};
Rooms.Update(room);
return room;
}
public static Room? SetRoomWarningMask(long roomId, WarningMaskType warningMask)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.WarningMask = warningMask;
Rooms.Update(room);
return room;
}
public static Room? SetRoomCustomWarning(long roomId, string? customWarning)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.CustomWarning = customWarning;
Rooms.Update(room);
return room;
}
public static Room? SetRoomTags(List<string> tags, long roomId, int type = 0)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
var newTags = tags
.Select(t => t.Trim().ToLowerInvariant())
.Where(t => !string.IsNullOrWhiteSpace(t))
.Distinct()
.ToList();
if (room.Tags == null) room.Tags = new List<Tags>();
foreach (var tag in newTags)
{
var existing = room.Tags.FirstOrDefault(t => t.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase));
if (existing != null)
existing.Type = (TagType)type;
else
room.Tags.Add(new Tags { Tag = tag, Type = (TagType)type });
}
Rooms.Update(room);
return room;
}
public static Room? SetRoomTagsMixed(List<RoomDBClasses.Tags> incomingTags, long roomId)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
room.Tags ??= new List<Tags>();
bool autoTagOnly = incomingTags.Count == 1 && incomingTags[0].Type == TagType.Auto;
foreach (var incoming in incomingTags)
{
var tag = incoming.Tag.Trim().ToLowerInvariant();
if (string.IsNullOrWhiteSpace(tag)) continue;
var existing = room.Tags.FirstOrDefault(t => t.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase));
if (incoming.Type == TagType.Auto && !autoTagOnly)
{
if (existing == null)
room.Tags.Add(new Tags { Tag = tag, Type = incoming.Type });
continue;
}
if (existing != null)
room.Tags.Remove(existing);
else
room.Tags.Add(new Tags { Tag = tag, Type = incoming.Type });
}
Rooms.Update(room);
return room;
}
public static Room? SetRoomSubroomRoomFile(
long roomId, long subRoomId,
string dataBlob, string description,
long playerId, int platform, int deviceClass)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
var subRoom = room.SubRooms?.FirstOrDefault(s => s.SubRoomId == subRoomId);
if (subRoom == null) return room;
subRoom.DataBlob = dataBlob;
subRoom.SavedByAccountId = playerId;
var save = new currentSave
{
SubRoomDataSaveId = GetNextSubRoomSaveId(),
RoomId = roomId,
SubRoomId = subRoomId,
DataBlob = dataBlob,
Description = description ?? "",
SavedByAccountId = playerId,
SavedOnPlatform = platform,
SavedOnDeviceClass = deviceClass,
CreatedAt = DateTime.UtcNow
};
SubRoomSaves.Insert(save);
Rooms.Update(room);
return room;
}
public static (List<currentSave> Results, int TotalResults) GetSubRoomSaves(
long roomId, long subRoomId, int skip, int take, string? search = null)
{
take = Math.Min(take, 9999);
var query = SubRoomSaves.FindAll()
.Where(s => s.RoomId == roomId && s.SubRoomId == subRoomId);
if (!string.IsNullOrWhiteSpace(search))
{
var q = search.ToLowerInvariant();
query = query.Where(s =>
(s.DataBlob ?? "").ToLower().Contains(q) ||
(s.Description ?? "").ToLower().Contains(q) ||
(s.Tags != null && s.Tags.Any(t => t.ToLower().Contains(q)))
);
}
var ordered = query.OrderByDescending(s => s.CreatedAt).ToList();
return (ordered.Skip(skip).Take(take).ToList(), ordered.Count);
}
public static currentSave? GetSubRoomSaveById(long roomId, long subRoomId, long saveId)
=> SubRoomSaves.FindOne(s => s.RoomId == roomId && s.SubRoomId == subRoomId && s.SubRoomDataSaveId == saveId);
public static Room? CreateSubroomForRoom(long roomId, string subroomName)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
if (room.SubRooms == null) room.SubRooms = new List<SubRooms>();
var lastSub = room.SubRooms.OrderByDescending(s => s.SubRoomId).FirstOrDefault();
var newSub = new SubRooms
{
RoomId = roomId,
Name = subroomName,
SubRoomId = GetNextSubRoomId(),
Accessibility = RoomAccessibility.Public,
UnitySceneId = "a75f7547-79eb-47c6-8986-6767abcb4f92",
MaxPlayers = 20
};
room.SubRooms.Add(newSub);
Rooms.Update(room);
return room;
}
public static bool DeleteSubroom(long roomId, long subRoomId)
{
var room = Rooms.FindById(roomId);
if (room == null) return false;
var sub = room.SubRooms?.FirstOrDefault(s => s.SubRoomId == subRoomId);
if (sub == null) return false;
room.SubRooms.Remove(sub);
if (room.SubRooms.Count < 1)
{
SubRoomSaves.DeleteMany(s => s.RoomId == roomId);
Rooms.Delete(roomId);
return true;
}
Rooms.Update(room);
return true;
}
public static bool ModifySubroom(long roomId, long subRoomId, string name, RoomAccessibility accessibility, int maxPlayers)
{
var room = Rooms.FindById(roomId);
if (room == null) return false;
var sub = room.SubRooms?.FirstOrDefault(s => s.SubRoomId == subRoomId);
if (sub == null) return false;
sub.Name = name;
sub.Accessibility = accessibility;
sub.MaxPlayers = maxPlayers;
Rooms.Update(room);
return true;
}
public static void IncrementRoomVisitCount(long roomId)
{
var room = Rooms.FindById(roomId);
if (room == null) return;
if (room.Stats == null) room.Stats = new Stats();
room.Stats.VisitCount++;
Rooms.Update(room);
}
public static void IncrementRoomCheerCount(long roomId)
{
var room = Rooms.FindById(roomId);
if (room == null) return;
room.Stats.CheerCount++;
Rooms.Update(room);
}
public static void DecrementRoomCheerCount(long roomId)
{
var room = Rooms.FindById(roomId);
if (room == null || room.Stats.CheerCount <= 0) return;
room.Stats.CheerCount--;
Rooms.Update(room);
}
public static void IncrementRoomFavoriteCount(long roomId)
{
var room = Rooms.FindById(roomId);
if (room == null) return;
room.Stats.FavoriteCount++;
Rooms.Update(room);
}
public static void DecrementRoomFavoriteCount(long roomId)
{
var room = Rooms.FindById(roomId);
if (room == null || room.Stats.FavoriteCount <= 0) return;
room.Stats.FavoriteCount--;
Rooms.Update(room);
}
public static bool UserCanEditRoom(long roomId, long accountId, bool allowCoOwner = true)
{
var room = Rooms.FindById(roomId);
if (room == null) return false;
if (room.CreatorAccountId == accountId) return true;
if (allowCoOwner) {
return room.Roles?.Any(r =>
r.AccountId == accountId &&
(r.Role == Role.CoOwner || r.Role == Role.Creator || r.Role == Role.TemporaryCoOwner)
) ?? false;
} else {
return room.Roles?.Any(r =>
r.AccountId == accountId &&
r.Role == Role.Creator
) ?? false;
}
}
public static bool AddRoleToRoom(long roomId, long accountId, Role role, Role invitedRole = Role.None)
{
var room = Rooms.FindById(roomId);
if (room == null) return false;
room.Roles ??= new List<Roles>();
room.Roles.Add(new Roles { AccountId = accountId, Role = role, InvitedRole = invitedRole });
Rooms.Update(room);
return true;
}
public static bool RemoveRoleFromRoom(long roomId, long accountId)
{
var room = Rooms.FindById(roomId);
if (room == null || room.Roles == null) return false;
room.Roles.RemoveAll(r => r.AccountId == accountId);
Rooms.Update(room);
return true;
}
public static long? CloneRoom(long roomId, string roomName, long playerId)
{
var original = GetRoom(roomId);
if (original == null)
return null;
if (original.Name == "DormRoom")
return null;
bool isOwner = original.CreatorAccountId == playerId;
bool isCoOwner = original.Roles?.Any(r =>
r.AccountId == playerId &&
(r.Role == Role.CoOwner || r.Role == Role.Creator)
) ?? false;
bool canClone = original.CloningAllowed;
bool isBaseRoom = original.Tags != null &&
original.Tags.Any(t => string.Equals(t.Tag, "base", StringComparison.OrdinalIgnoreCase));
bool isRRO = original.Tags != null &&
original.Tags.Any(t =>
string.Equals(t.Tag, "rro", StringComparison.OrdinalIgnoreCase) ||
string.Equals(t.Tag, "recroomoriginal", StringComparison.OrdinalIgnoreCase));
if (isRRO && !isBaseRoom && !canClone)
return null;
if (!isBaseRoom && !isOwner && !isCoOwner && !canClone)
return null;
var originalSubroomOrder = (original.SubRooms ?? new List<SubRooms>())
.Select(s => s.SubRoomId)
.ToList();
var latestSavePerSubroom = new Dictionary<long, currentSave>();
foreach (var origSubId in originalSubroomOrder)
{
var latestSave = SubRoomSaves
.Find(s => s.RoomId == roomId && s.SubRoomId == origSubId)
.OrderByDescending(s => s.CreatedAt)
.FirstOrDefault();
if (latestSave != null)
latestSavePerSubroom[origSubId] = latestSave;
}
var json = System.Text.Json.JsonSerializer.Serialize(original);
var clone = System.Text.Json.JsonSerializer.Deserialize<Room>(json);
if (clone == null)
return null;
clone.RoomId = 0;
clone.Name = roomName;
clone.CreatedAt = DateTime.UtcNow;
clone.ImageName = "DefaultRoomImage.jpg";
clone.Accessibility = RoomAccessibility.Private;
clone.CloningAllowed = false;
clone.IsRRO = false;
clone.IsDeveloperOwned = false;
clone.IsDorm = false;
clone.PromoImages = new List<string>();
clone.Stats = new Stats();
clone.CreatorAccountId = playerId;
clone.Tags = (clone.Tags ?? new List<Tags>())
.Where(t =>
!string.Equals(t.Tag, "base", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(t.Tag, "rro", StringComparison.OrdinalIgnoreCase))
.ToList();
clone.Roles = new List<Roles>
{
new Roles
{
AccountId = playerId,
Role = Role.Creator,
InvitedRole = Role.Creator
}
};
if (clone.SubRooms != null)
{
foreach (var sub in clone.SubRooms)
{
sub.SubRoomId = GetNextSubRoomId();
sub.RoomId = clone.RoomId;
sub.SavedByAccountId = playerId;
if (sub.CreatorAccountId != null)
sub.CreatorAccountId = playerId;
}
}
long newRoomId = AddRoomSync(clone, shouldAssignNewIds: false);
var insertedRoom = GetRoom(newRoomId);
if (insertedRoom?.SubRooms != null)
{
foreach (var sub in insertedRoom.SubRooms)
sub.RoomId = newRoomId;
Rooms.Update(insertedRoom);
}
var clonedSubrooms = insertedRoom?.SubRooms ?? new List<SubRooms>();
for (int i = 0; i < originalSubroomOrder.Count && i < clonedSubrooms.Count; i++)
{
long originalSubroomId = originalSubroomOrder[i];
long clonedSubroomId = clonedSubrooms[i].SubRoomId;
if (latestSavePerSubroom.TryGetValue(originalSubroomId, out var latestOriginalSave))
{
var clonedSave = new currentSave
{
SubRoomDataSaveId = GetNextSubRoomSaveId(),
RoomId = newRoomId,
SubRoomId = clonedSubroomId,
DataBlob = latestOriginalSave.DataBlob,
DataBlobHash = latestOriginalSave.DataBlobHash,
ReferencedUnityAssetIds = latestOriginalSave.ReferencedUnityAssetIds != null
? new List<string>(latestOriginalSave.ReferencedUnityAssetIds)
: new List<string>(),
UnitySubAssets = latestOriginalSave.UnitySubAssets != null
? new List<string>(latestOriginalSave.UnitySubAssets)
: new List<string>(),
ReferencedUnityAssets = latestOriginalSave.ReferencedUnityAssets != null
? new List<string>(latestOriginalSave.ReferencedUnityAssets)
: new List<string>(),
UnityAssetId = latestOriginalSave.UnityAssetId,
PersistenceVersion = latestOriginalSave.PersistenceVersion,
OMVersion = latestOriginalSave.OMVersion,
UgcSubVersion = latestOriginalSave.UgcSubVersion,
SavedByAccountId = playerId,
SavedOnPlatform = latestOriginalSave.SavedOnPlatform,
SavedOnDeviceClass = latestOriginalSave.SavedOnDeviceClass,
Description = $"Cloned from room ^{original.Name}",
Tags = latestOriginalSave.Tags != null
? new List<string>(latestOriginalSave.Tags)
: new List<string>(),
ModerationState = latestOriginalSave.ModerationState,
CreatedAt = DateTime.UtcNow
};
SubRoomSaves.Insert(clonedSave);
var room = Rooms.FindById(newRoomId);
var sub = room?.SubRooms?.FirstOrDefault(s => s.SubRoomId == clonedSubroomId);
if (sub != null)
{
sub.DataBlob = clonedSave.DataBlob;
sub.SavedByAccountId = playerId;
sub.CurrentSave = clonedSave;
Rooms.Update(room);
}
}
}
for (int i = 0; i < originalSubroomOrder.Count && i < clonedSubrooms.Count; i++)
{
long originalSubroomId = originalSubroomOrder[i];
long clonedSubroomId = clonedSubrooms[i].SubRoomId;
var originalPermissions = PhotonAccessTokenDB.GetPermissions(roomId, originalSubroomId);
if (originalPermissions != null && originalPermissions.Permissions.Count > 0)
{
var copiedEntries = originalPermissions.Permissions.Select(p => new PhotonAccessTokenDBClasses.StoredPermissionEntry
{
Permission = p.Permission,
Role = p.Role,
Type = p.Type,
Override = p.Override,
Value = p.Value
}).ToList();
PhotonAccessTokenDB.SetPermissions(newRoomId, clonedSubroomId, copiedEntries);
}
}
return newRoomId;
}
public static SubRooms? CloneSubroom(long roomId, long subRoomId, long playerId)
{
var room = Rooms.FindById(roomId);
if (room == null) return null;
var original = room.SubRooms?.FirstOrDefault(s => s.SubRoomId == subRoomId);
if (original == null) return null;
string GenerateUniqueName()
{
string candidate;
do
{
int suffix = Random.Shared.Next(10000, 999999);
candidate = $"{original.Name}_{suffix}";
} while (room.SubRooms!.Any(s => s.Name.Equals(candidate, StringComparison.OrdinalIgnoreCase)));
return candidate;
}
var newSub = new SubRooms
{
SubRoomId = GetNextSubRoomId(),
RoomId = roomId,
Name = GenerateUniqueName(),
Accessibility = original.Accessibility,
IsSandbox = original.IsSandbox,
MaxPlayers = original.MaxPlayers,
UnitySceneId = original.UnitySceneId,
DataBlob = original.DataBlob,
SavedByAccountId = playerId,
ShouldAutoStageSaves = original.ShouldAutoStageSaves,
StagedSubRoomDataSaveId = original.StagedSubRoomDataSaveId,
LastModeratedSaveModerationState = original.LastModeratedSaveModerationState,
CreatorAccountId = playerId
};
var latestSave = SubRoomSaves
.Find(s => s.RoomId == roomId && s.SubRoomId == subRoomId)
.OrderByDescending(s => s.CreatedAt)
.FirstOrDefault();
if (latestSave != null)
{
var clonedSave = new currentSave
{
SubRoomDataSaveId = GetNextSubRoomSaveId(),
RoomId = roomId,
SubRoomId = newSub.SubRoomId,
DataBlob = latestSave.DataBlob,
DataBlobHash = latestSave.DataBlobHash,
ReferencedUnityAssetIds = latestSave.ReferencedUnityAssetIds != null ? new List<string>(latestSave.ReferencedUnityAssetIds) : new List<string>(),
UnitySubAssets = latestSave.UnitySubAssets != null ? new List<string>(latestSave.UnitySubAssets) : new List<string>(),
ReferencedUnityAssets = latestSave.ReferencedUnityAssets != null ? new List<string>(latestSave.ReferencedUnityAssets) : new List<string>(),
UnityAssetId = latestSave.UnityAssetId,
PersistenceVersion = latestSave.PersistenceVersion,
OMVersion = latestSave.OMVersion,
UgcSubVersion = latestSave.UgcSubVersion,
SavedByAccountId = playerId,
SavedOnPlatform = latestSave.SavedOnPlatform,
SavedOnDeviceClass = latestSave.SavedOnDeviceClass,
// Description = "$"Cloned from subroom ^{original.Name}"",
Description = "",
Tags = latestSave.Tags != null ? new List<string>(latestSave.Tags) : new List<string>(),
ModerationState = latestSave.ModerationState,
CreatedAt = DateTime.UtcNow
};
SubRoomSaves.Insert(clonedSave);
newSub.CurrentSave = clonedSave;
newSub.DataBlob = clonedSave.DataBlob;
}
room.SubRooms!.Add(newSub);
Rooms.Update(room);
return newSub;
}
public static long CloneDormRoom(long playerId)
{
var baseRoom = GetRoom(1);
if (baseRoom == null) throw new Exception("Base dorm room not found.");
if (baseRoom.SubRooms == null || !baseRoom.SubRooms.Any()) throw new Exception("Base dorm room has no subrooms.");
var json = System.Text.Json.JsonSerializer.Serialize(baseRoom);
var clone = System.Text.Json.JsonSerializer.Deserialize<Room>(json);
if (clone == null) throw new Exception("Failed to clone dorm room.");
clone.RoomId = 0;
clone.CreatorAccountId = playerId;
clone.CloningAllowed = false;
clone.Name = "DormRoom";
clone.ImageName = "DefaultRoomImage.jpg";
clone.CreatedAt = DateTime.UtcNow;
clone.Accessibility = RoomAccessibility.Private;
clone.IsRRO = false;
clone.IsDorm = true;
clone.Stats = new Stats();
clone.Roles = new List<Roles>
{
new Roles { AccountId = playerId, Role = Role.Creator, InvitedRole = Role.Creator }
};
if (clone.SubRooms != null)
{
foreach (var sub in clone.SubRooms)
{
sub.SubRoomId = GetNextSubRoomId();
sub.RoomId = 0;
sub.SavedByAccountId = playerId;
sub.CreatorAccountId = playerId;
}
}
return AddRoomSync(clone);
}
public static void DeleteRoom(long roomId)
{
SubRoomSaves.DeleteMany(s => s.RoomId == roomId);
Rooms.Delete(new BsonValue(roomId));
}
public static void DeleteAllRoomsFromPlayerId(long creatorId)
{
var rooms = Rooms.Find(r => r.CreatorAccountId == creatorId).ToList();
foreach (var room in rooms)
{
SubRoomSaves.DeleteMany(s => s.RoomId == room.RoomId);
Rooms.Delete(room.RoomId);
}
Console.WriteLine($"[DB] Deleted {rooms.Count} rooms for creator {creatorId}.");
}
public static Room? GetPlayerDormRoom(long playerId)
{
return Rooms.FindOne(r => r.IsDorm && r.CreatorAccountId == playerId);
}
public static object? GetRoomSaveById(
ulong roomId,
ulong subroomId,
ulong subRoomDataSaveId,
int target,
int version)
{
var save = SubRoomSaves.FindOne(s =>
s.RoomId == (long)roomId &&
s.SubRoomId == (long)subroomId &&
s.SubRoomDataSaveId == (long)subRoomDataSaveId);
if (save == null)
return null;
return new
{
save.SubRoomDataSaveId,
save.RoomId,
save.SubRoomId,
save.DataBlob,
save.Description,
save.CreatedAt,
Target = target,
Version = version
};
}
public static RoomDBClasses.SubroomSaveResults? GetRoomSaves(ulong roomId, ulong subroomId, int skip = 0, int take = 20)
{
var saves = SubRoomSaves.Find(x => x.RoomId == (long)roomId && x.SubRoomId == (long)subroomId)
.OrderByDescending(x => x.CreatedAt)
.ToList();
var paged = saves
.Skip(skip)
.Take(take)
.ToList();
return new SubroomSaveResults
{
Results = paged,
TotalCount = saves.Count
};
}
public static void FixDuplicateDorms()
{
var dormRooms = Rooms.Find(r => r.IsDorm).ToList();
var grouped = dormRooms.GroupBy(r => r.CreatorAccountId).Where(g => g.Count() > 1);
foreach (var group in grouped)
{
var dormToKeep = group.OrderBy(r => r.CreatedAt).First();
foreach (var room in group.Where(r => r.RoomId != dormToKeep.RoomId))
{
SubRoomSaves.DeleteMany(s => s.RoomId == room.RoomId);
Rooms.Delete(room.RoomId);
Console.WriteLine($"[DB] Deleted duplicate dorm {room.RoomId} for creator {room.CreatorAccountId}");
}
Console.WriteLine($"[DB] Kept dorm {dormToKeep.RoomId} for creator {group.Key}");
}
}
public static bool DoesPlayerDormExist(long playerId)
{
var dorm = Rooms.FindOne(r => r.CreatorAccountId == playerId && r.IsDorm);
return dorm != null;
}
}
}