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

View File

@@ -1,8 +1,218 @@
/* 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 { it } from "node:test";
import { Redis } from "../../db.ts";
import { RootPath } from "./baseimages.ts";
import { BuiltinRoom, RoomState } from "./roomtypes.ts";
import { RoomFetch } from "./room.ts";
const rooms = JSON.parse(Deno.readTextFileSync(`${RootPath}/res/rooms.json`)) as BuiltinRoom[];
class RoomsBase {
readonly roomMetaKeys = { // hash keys
RoomId: "id",
Name: "name",
Description: "desc",
CreatorPlayerId: "creatorId",
ImageName: "imagename",
State: "state",
Accessibility: "access",
SupportsLevelVoting: "levelvoting",
IsAGRoom: "isagroom",
IsDormRoom: "isdorm",
CloningAllowed: "cloneable",
SupportsScreens: "can-screen",
SupportsWalkVR: "can-walkvr",
SupportsTeleportVR: "can-televr",
AllowsJuniors: "juniors",
RoomWarningMask: "warningmask",
CustomRoomWarning: "warning",
DisableMicAutoMute: "disableautomute"
}
readonly subroomMetaKeys = { // hash keys
Name: "name",
RoomSceneLocationId: "location",
IsSandbox: "issandbox",
CanMatchmakeInto: "matchmakeable",
RoomSceneId: "sceneid",
DataBlobName: "datablob",
MaxPlayers: "playercap",
DataModifiedAt: "modifiedat"
}
readonly roomRootKeys = {
CheerCount: "cheers", // string
CheerPids: "cheers-players", // set
VisitCount: "visits", // string
FavoriteCount: "favorites", // string
FavoritePids: "favorites-players", // set
Subrooms: "subrooms", // set
Meta: "roommeta" // hash
}
readonly subroomRootKeys = {
Meta: "scenemeta"
}
readonly miscKeys = {
BuiltinGenerated: "builtinrooms-done",
AGRooms: "agrooms"
}
getAllBuiltinRooms() {
return rooms;
}
async get(id: number) {
try {
return await new RoomFetch({ roomId: id }).fetch();
} catch {
return null;
}
}
async getByName(name: string) {
try {
return await new RoomFetch({ roomName: name }).fetch();
} catch {
return null;
}
}
async getAllBuiltinRoomGenerations() {
const ids = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.AGRooms));
const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
return (await Promise.all(parsedIds.map(id => this.get(id)))).filter(val => val !== null);
}
async #getAvailableRoomId() {
let id = Math.round(Math.random() * Math.pow(2, 31));
while ((await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Rooms.Root, id.toString(), this.roomRootKeys.Meta))) >= 1)
id = await this.#getAvailableRoomId();
return id;
}
async #getAvailableSubRoomId(roomid: number) {
let id = Math.round(Math.random() * Math.pow(2, 31));
while ((await Redis.Database.exists(
Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
roomid.toString(),
this.roomRootKeys.Subrooms,
id.toString(),
this.subroomRootKeys.Meta
))) >= 1)
id = await this.#getAvailableSubRoomId(roomid);
return id;
}
async generateBuiltinRooms() {
if ((await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.BuiltinGenerated))) !== null) return true;
for (const builtinRoom of rooms) {
const newId = await this.#getAvailableRoomId();
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.AGRooms), newId);
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, builtinRoom.Name), newId);
const rootKey = Redis.buildKey(Redis.KeyGroups.Rooms.Root, newId.toString());
const roomMetaRootKey = Redis.buildKey(rootKey, this.roomRootKeys.Meta);
await Promise.all([
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomId, newId),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Name, builtinRoom.Name),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Description, builtinRoom.Description),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CreatorPlayerId, `1`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.ImageName, `${builtinRoom.Name}.png`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.State, `${RoomState.Active}`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Accessibility, `${builtinRoom.Accessibility}`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsLevelVoting, `${builtinRoom.SupportsLevelVoting}`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.IsAGRoom, "true"),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.IsDormRoom, "false"),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CloningAllowed, `${builtinRoom.CloningAllowed}`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsScreens, `${builtinRoom.SupportsScreens}`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsWalkVR, `${builtinRoom.SupportsWalkVR}`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsTeleportVR, `${builtinRoom.SupportsTeleportVR}`),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.AllowsJuniors, "true"),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomWarningMask, 0),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CustomRoomWarning, ""),
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.DisableMicAutoMute, "false"),
]);
for (const subroom of builtinRoom.Scenes) {
const newSubId = await this.#getAvailableSubRoomId(newId);
const subRootMetaKey = Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
newId.toString(),
this.roomRootKeys.Subrooms,
newSubId.toString(),
this.subroomRootKeys.Meta
);
await Promise.all([
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.RoomSceneLocationId, subroom.RoomSceneLocationId),
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.Name, subroom.RoomSceneLocationId),
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.IsSandbox, `${subroom.IsSandbox}`),
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.DataBlobName, ""),
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.MaxPlayers, subroom.MaxPlayers),
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.CanMatchmakeInto, `${subroom.CanMatchmakeInto}`),
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.DataModifiedAt, new Date().toISOString()),
]);
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, newId.toString(), this.roomRootKeys.Subrooms), newSubId);
}
}
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.BuiltinGenerated), "1");
return false;
}
async getIdFromName(name: string) {
const unparsedId = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Room_Names, name));
if (!unparsedId) return null;
const parsedId = parseInt(unparsedId);
if (isNaN(parsedId)) return null;
return parsedId;
}
async getNameFromId(id: number) {
const name = await Redis.Database.hget(Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
id.toString(),
this.roomRootKeys.Meta
), this.roomMetaKeys.Name);
if (!name) return null;
return name;
}
async getSubroomIdsFromRoom(id: number): Promise<string[]>;
async getSubroomIdsFromRoom(id: number, stringify: false): Promise<number[]>;
async getSubroomIdsFromRoom(id: number, stringify: boolean | undefined = false): Promise<string[] | number[]> {
const ids = await Redis.Database.smembers(Redis.buildKey(
Redis.KeyGroups.Rooms.Root,
id.toString(),
this.roomRootKeys.Subrooms
));
const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
if (!stringify) return parsedIds;
else return parsedIds.map(val => val.toString());
}
}
const Rooms = new RoomsBase();
export { rooms as BuiltinRooms };
export default Rooms;