Rooms and matchmaking

* Added missing config (route) data
* Fetch room class, database hash
* Basic room querying
* Built-in room generation during first runtime
* Matchmaking response base and notes for myself, later today
* Instance fixes
* Challenge and quick play routes
(unused for now)
* Rooms route (untested)
* Matchmaking goto route
* Avatar route addition
* Settings/set route
This commit is contained in:
2025-04-02 00:27:10 -04:00
parent e9830dcb19
commit 27b3754330
68 changed files with 1745 additions and 27 deletions

132
src/data/content/room.ts Normal file
View File

@@ -0,0 +1,132 @@
/* Galvanic Corrosion - Rec Room custom server for communities.
<https://gitea.proxnet.dev/zombieb-galvanic-corrosion>
Copyright (C) 2025 @zombieb (Discord / proxnet Gitea)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */
import { Redis } from "../../db.ts";
import Rooms from "./rooms.ts";
import { IntegratedRoomScene, RoomAccessibility, RoomDetails, RoomState } from "./roomtypes.ts";
interface RoomFetchOptions {
roomName?: string,
roomId?: number
}
export function parseBooleanDefault(obj: string, def: boolean | undefined = false) {
try {
return JSON.parse(obj) as boolean;
} catch {
return def;
}
}
export class RoomFetch {
roomId: number | null = null;
roomName: string | null = null;
constructor(options: RoomFetchOptions) {
this.roomId = options.roomId ?? null;
this.roomName = options.roomName ?? null;
}
async fetch() {
if (!this.roomId && this.roomName) {
const givenId = await Rooms.getIdFromName(this.roomName);
if (!givenId) return null;
else this.roomId = givenId;
} else if (!this.roomName && this.roomId) {
const givenName = await Rooms.getNameFromId(this.roomId);
if (!givenName) return null;
else this.roomName = givenName;
} else if (!this.roomId && !this.roomName) return null;
const roomRootKey = Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
this.roomId!.toString(), // code above takes care of null possibility
);
const roomMetaKey = Redis.buildKey(
roomRootKey,
Rooms.roomRootKeys.Meta
);
const [ hash, cheerCount, favoriteCount, visitCount ] = await Promise.all([
Redis.Database.hgetall(roomMetaKey),
Redis.Database.get(Redis.buildKey(roomRootKey, Rooms.roomRootKeys.CheerCount)),
Redis.Database.get(Redis.buildKey(roomRootKey, Rooms.roomRootKeys.FavoriteCount)),
Redis.Database.get(Redis.buildKey(roomRootKey, Rooms.roomRootKeys.VisitCount)),
]);
const room: RoomDetails = {
Room: {
RoomId: hash[Rooms.roomMetaKeys.RoomId] ? parseInt(hash[Rooms.roomMetaKeys.RoomId]) : 0,
Name: hash[Rooms.roomMetaKeys.Name] ?? "DATABASEERROR",
Description: hash[Rooms.roomMetaKeys.Description] ?? "DATABASEERROR",
CreatorPlayerId: hash[Rooms.roomMetaKeys.CreatorPlayerId] ? parseInt(hash[Rooms.roomMetaKeys.CreatorPlayerId]) : 1,
ImageName: hash[Rooms.roomMetaKeys.ImageName] ?? "DefaultProfileImage.png",
State: hash[Rooms.roomMetaKeys.State] ? parseInt(hash[Rooms.roomMetaKeys.State]) : RoomState.Active,
Accessibility: hash[Rooms.roomMetaKeys.Accessibility] ? parseInt(hash[Rooms.roomMetaKeys.Accessibility]) : RoomAccessibility.Unlisted,
SupportsLevelVoting: hash[Rooms.roomMetaKeys.SupportsLevelVoting] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.SupportsLevelVoting], true) : true,
IsAGRoom: hash[Rooms.roomMetaKeys.IsAGRoom] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.IsAGRoom], false) : false,
IsDormRoom: hash[Rooms.roomMetaKeys.IsDormRoom] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.IsDormRoom], false) : false,
CloningAllowed: hash[Rooms.roomMetaKeys.CloningAllowed] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.CloningAllowed], false) : false,
SupportsScreens: hash[Rooms.roomMetaKeys.SupportsScreens] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.SupportsScreens], true) : true,
SupportsWalkVR: hash[Rooms.roomMetaKeys.SupportsWalkVR] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.SupportsWalkVR], true) : true,
SupportsTeleportVR: hash[Rooms.roomMetaKeys.SupportsTeleportVR] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.SupportsTeleportVR], true) : true,
AllowsJuniors: hash[Rooms.roomMetaKeys.AllowsJuniors] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.AllowsJuniors], true) : true,
RoomWarningMask: hash[Rooms.roomMetaKeys.RoomWarningMask] ? parseInt(hash[Rooms.roomMetaKeys.RoomWarningMask]) : 0,
CustomRoomWarning: hash[Rooms.roomMetaKeys.CustomRoomWarning] ?? "",
DisableMicAutoMute: hash[Rooms.roomMetaKeys.DisableMicAutoMute] ? parseBooleanDefault(hash[Rooms.roomMetaKeys.DisableMicAutoMute], false) : undefined,
},
Scenes: [], // temporary
CoOwners: [], // temporary
InvitedCoOwners: [], // temporary
Hosts: [], // temporary
InvitedHosts: [], // temporary
CheerCount: cheerCount ? parseInt(cheerCount) : 0,
FavoriteCount: favoriteCount ? parseInt(favoriteCount) : 0,
VisitCount: visitCount ? parseInt(visitCount) : 0,
Tags: [] // temporary
}
const subrooms = await Rooms.getSubroomIdsFromRoom(this.roomId!);
for (const subroom of subrooms) {
const subroomMetaKey = Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
this.roomId!.toString(),
Rooms.roomRootKeys.Subrooms,
subroom.toString(),
Rooms.subroomRootKeys.Meta
);
const subroomDetails = await Redis.Database.hgetall(subroomMetaKey);
room.Scenes.push({
RoomSceneId: parseInt(subroom),
RoomId: this.roomId ?? 0,
RoomSceneLocationId: subroomDetails[Rooms.subroomMetaKeys.RoomSceneLocationId] ?? IntegratedRoomScene.MakerRoom,
Name: subroomDetails[Rooms.subroomMetaKeys.Name] ?? "DATABASE ERROR",
IsSandbox: subroomDetails[Rooms.subroomMetaKeys.IsSandbox] ? parseBooleanDefault(subroomDetails[Rooms.subroomMetaKeys.IsSandbox], false) : false,
CanMatchmakeInto: subroomDetails[Rooms.subroomMetaKeys.IsSandbox] ? parseBooleanDefault(subroomDetails[Rooms.subroomMetaKeys.IsSandbox], true) : undefined,
DataBlobName: subroomDetails[Rooms.subroomMetaKeys.DataBlobName] ?? "",
MaxPlayers: subroomDetails[Rooms.subroomMetaKeys.MaxPlayers] ? parseInt(subroomDetails[Rooms.subroomMetaKeys.MaxPlayers]) : 1,
DataModifiedAt: subroomDetails[Rooms.subroomMetaKeys.DataModifiedAt] ?? new Date().toISOString()
});
}
return room;
}
}