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:
@@ -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;
|
||||
Reference in New Issue
Block a user