Frostbite is gone????
Some checks failed
Galvanic Corrosion Cross-Compile / build (push) Failing after 38s
Some checks failed
Galvanic Corrosion Cross-Compile / build (push) Failing after 38s
* Rewrite rooms backend, "RoomFactory" and "SubroomFactory"
- Used for modifying and fetching rooms
* Progression and reputation bulk endpoints
* Announcement endpoint temp
* OOBE is now the only initial pref key
- Will be removed in the future when cohortnux is implemented
* Misc minor fixes and clarifications
* Simplified namegen dictionary
- The previous one was generated with ChatGPT, hence the duplicated strings. I googled "random username generator" and borrowed a random result's generation dictionary.
* QuickPlay support with "initialRoom" in config (untested)
This commit is contained in:
@@ -56,6 +56,9 @@ Ideally, this should be unique for every server, and can be chosen by the server
|
|||||||
`photonRegionId`: The region to connect to when using Photon Cloud. When using the self-hosted PhotonSocketServer,<br>
|
`photonRegionId`: The region to connect to when using Photon Cloud. When using the self-hosted PhotonSocketServer,<br>
|
||||||
this can be anything *except* for "none" or 4, since there is only one server to connect to and the game uses offline mode when the region ID is set to none.
|
this can be anything *except* for "none" or 4, since there is only one server to connect to and the game uses offline mode when the region ID is set to none.
|
||||||
|
|
||||||
|
`initialRoom`: On game startup, redirects the player to this room name instead of their DormRoom. Set to null if a "natural" startup is preferred.<br>
|
||||||
|
Ideally, this room should not be private and should be matchmakeable.
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
These three values expose booleans you can change to enable/disable logging various messages sent by the server used for debugging or troubleshooting purposes.
|
These three values expose booleans you can change to enable/disable logging various messages sent by the server used for debugging or troubleshooting purposes.
|
||||||
|
|
||||||
|
|||||||
49
res/roomgen.ts
Normal file
49
res/roomgen.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163",
|
||||||
|
RecCenter = "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
||||||
|
3DCharades = "4078dfed-24bb-4db7-863f-578ba48d726b",
|
||||||
|
DiscGolfLake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||||
|
DiscGolfPropulsion = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||||
|
Dodgeball = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||||
|
Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499",
|
||||||
|
Paintball_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||||
|
Paintball_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||||
|
Paintball_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||||
|
Paintball_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||||
|
Paintball_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||||
|
Paintball_Drive-in = "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3",
|
||||||
|
PaintballVR_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||||
|
PaintballVR_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||||
|
PaintballVR_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||||
|
PaintballVR_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||||
|
PaintballVR_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||||
|
GoldenTrophy = "91e16e35-f48f-4700-ab8a-a1b79e50e51b",
|
||||||
|
TheRiseofJumbotron = "acc06e66-c2d0-4361-b0cd-46246a4c455c",
|
||||||
|
CrimsonCauldron = "949fa41f-4347-45c0-b7ac-489129174045",
|
||||||
|
IsleOfLostSkulls = "7e01cfe0-820a-406f-b1b3-0a5bf575235c",
|
||||||
|
Soccer = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||||
|
LaserTag_Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||||
|
LaserTag_CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||||
|
RecRoyaleSquads = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
||||||
|
RecRoyaleSolos = "b010171f-4875-4e89-baba-61e878cd41e1",
|
||||||
|
Lounge = "a067557f-ca32-43e6-b6e5-daaec60b4f5a",
|
||||||
|
PerformanceHall = "9932f88f-3929-43a0-a012-a40b5128e346",
|
||||||
|
MakerRoom = "a75f7547-79eb-47c6-8986-6767abcb4f92",
|
||||||
|
Park = "0a864c86-5a71-4e18-8041-8124e4dc9d98",
|
||||||
|
River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||||
|
Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||||
|
Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||||
|
Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||||
|
Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||||
|
Lake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||||
|
PropulsionTestRange = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||||
|
Gym = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||||
|
Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||||
|
Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||||
|
CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||||
|
DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||||
|
Crescendo = "49cb8993-a956-43e2-86f4-1318f279b22a",
|
||||||
|
Bowling = "ae929543-9a07-41d5-8ee9-dbbee8c36800",
|
||||||
|
BowlingAlley = "ae929543-9a07-41d5-8ee9-dbbee8c36800",
|
||||||
|
StuntRunner_StuntRunner = "b7281665-a715-4051-826b-8e08e69c6172",
|
||||||
|
StuntRunner_TheMainEvent = "3a636bd2-f896-424c-9225-c184522c0d87",
|
||||||
|
StuntRunnerBaseRoom = "882e9b96-7115-4b03-86f6-c0c9d8e22e00",
|
||||||
@@ -50,6 +50,7 @@ type PublicConfiguration = {
|
|||||||
maxLevels: number;
|
maxLevels: number;
|
||||||
patches: string[];
|
patches: string[];
|
||||||
photonRegionId: PhotonRegionCodeString | PhotonRegionCodeNumber;
|
photonRegionId: PhotonRegionCodeString | PhotonRegionCodeNumber;
|
||||||
|
initialRoom: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type LoggingConfiguration = {
|
type LoggingConfiguration = {
|
||||||
@@ -112,6 +113,7 @@ export const defaultConfig: GalvanicConfiguration = {
|
|||||||
maxLevels: 30,
|
maxLevels: 30,
|
||||||
patches: [],
|
patches: [],
|
||||||
photonRegionId: PhotonRegionCodeNumber.us,
|
photonRegionId: PhotonRegionCodeNumber.us,
|
||||||
|
initialRoom: null
|
||||||
},
|
},
|
||||||
logging: {
|
logging: {
|
||||||
notfound: false,
|
notfound: false,
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -17,10 +17,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
|
|
||||||
import { Redis } from "../../db.ts";
|
import { Redis } from "../../db.ts";
|
||||||
import { RootPath } from "./baseimages.ts";
|
import { RootPath } from "./baseimages.ts";
|
||||||
import { BuiltinRoom, RoomAccessibility, RoomDetails, RoomState } from "./roomtypes.ts";
|
|
||||||
import { RoomFetch } from "./room.ts";
|
|
||||||
import { Profile } from "../profiles.ts";
|
import { Profile } from "../profiles.ts";
|
||||||
import Logging from "@proxnet/undead-logging";
|
import Logging from "@proxnet/undead-logging";
|
||||||
|
import { BuiltinRoom, FactoryMode, IntegratedRoomScene, RoomAccessibility, RoomDetails, RoomState, WriteMode } from "./rooms/DataTypes.ts";
|
||||||
|
import { RoomFactory } from "./rooms/RoomFactory.ts";
|
||||||
|
import { SubroomFactory } from "./rooms/SubroomFactory.ts";
|
||||||
|
import { Image } from "https://deno.land/x/imagescript@1.3.0/ImageScript.js";
|
||||||
|
|
||||||
const log = new Logging("Rooms");
|
const log = new Logging("Rooms");
|
||||||
|
|
||||||
@@ -28,51 +30,9 @@ const rooms = JSON.parse(Deno.readTextFileSync(`${RootPath}/res/rooms.json`)) as
|
|||||||
|
|
||||||
class RoomsBase {
|
class RoomsBase {
|
||||||
|
|
||||||
readonly roomMetaKeys = { // hash keys
|
static 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",
|
BuiltinGenerated: "builtinrooms-done",
|
||||||
AGRooms: "agrooms"
|
AGRooms: "agrooms",
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllBuiltinRooms() {
|
getAllBuiltinRooms() {
|
||||||
@@ -81,7 +41,9 @@ class RoomsBase {
|
|||||||
|
|
||||||
async get(id: number) {
|
async get(id: number) {
|
||||||
try {
|
try {
|
||||||
return await new RoomFetch({ roomId: id }).fetch();
|
const factory = await new RoomFactory({ id: id }).init();
|
||||||
|
if (!factory) return null;
|
||||||
|
return factory.export();
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -89,21 +51,23 @@ class RoomsBase {
|
|||||||
|
|
||||||
async getByName(name: string) {
|
async getByName(name: string) {
|
||||||
try {
|
try {
|
||||||
return await new RoomFetch({ roomName: name }).fetch();
|
const factory = await new RoomFactory({ name: name }).init();
|
||||||
|
if (!factory) return null;
|
||||||
|
return factory.export();
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllBuiltinRoomGenerations() {
|
async getAllBuiltinRoomGenerations() {
|
||||||
const ids = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.AGRooms));
|
const ids = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.AGRooms));
|
||||||
const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
|
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);
|
return (await Promise.all(parsedIds.map(id => this.get(id)))).filter(val => val !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getAvailableRoomId() {
|
async #getAvailableRoomId() {
|
||||||
let id = Math.round(Math.random() * Math.pow(2, 31));
|
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)
|
while ((await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Rooms.Root, id.toString(), RoomFactory.Keys.Meta))) >= 1)
|
||||||
id = await this.#getAvailableRoomId();
|
id = await this.#getAvailableRoomId();
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -114,94 +78,76 @@ class RoomsBase {
|
|||||||
Redis.buildKey(
|
Redis.buildKey(
|
||||||
Redis.KeyGroups.Rooms.Root,
|
Redis.KeyGroups.Rooms.Root,
|
||||||
roomid.toString(),
|
roomid.toString(),
|
||||||
this.roomRootKeys.Subrooms,
|
RoomFactory.Keys.Subrooms,
|
||||||
id.toString(),
|
id.toString(),
|
||||||
this.subroomRootKeys.Meta
|
SubroomFactory.Keys.Meta
|
||||||
))) >= 1)
|
))) >= 1)
|
||||||
id = await this.#getAvailableSubRoomId(roomid);
|
id = await this.#getAvailableSubRoomId(roomid);
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async #setRoom(details: RoomDetails) {
|
|
||||||
const rootKey = Redis.buildKey(Redis.KeyGroups.Rooms.Root, details.Room.RoomId.toString());
|
|
||||||
|
|
||||||
const roomMetaRootKey = Redis.buildKey(rootKey, this.roomRootKeys.Meta);
|
|
||||||
await Promise.all([
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomId, details.Room.RoomId),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Name, details.Room.Name),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Description, details.Room.Description),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CreatorPlayerId, details.Room.CreatorPlayerId),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.ImageName, details.Room.ImageName),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.State, details.Room.State),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Accessibility, details.Room.Accessibility),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsLevelVoting, `${details.Room.SupportsLevelVoting}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.IsAGRoom, `${details.Room.IsAGRoom}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.IsDormRoom, `${details.Room.IsDormRoom}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CloningAllowed, `${details.Room.CloningAllowed}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsScreens, `${details.Room.SupportsScreens}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsWalkVR, `${details.Room.SupportsWalkVR}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsTeleportVR, `${details.Room.SupportsTeleportVR}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.AllowsJuniors, `${details.Room.AllowsJuniors}`),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomWarningMask, details.Room.RoomWarningMask),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CustomRoomWarning, details.Room.CustomRoomWarning),
|
|
||||||
Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.DisableMicAutoMute, `${details.Room.DisableMicAutoMute ? details.Room.DisableMicAutoMute : 'null'}`),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (const subroom of details.Scenes) {
|
|
||||||
const newSubId = await this.#getAvailableSubRoomId(details.Room.RoomId);
|
|
||||||
const subRootMetaKey = Redis.buildKey(
|
|
||||||
Redis.KeyGroups.Rooms.Root,
|
|
||||||
details.Room.RoomId.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.Name),
|
|
||||||
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.IsSandbox, `${subroom.IsSandbox}`),
|
|
||||||
Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.DataBlobName, subroom.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, details.Room.RoomId.toString(), this.roomRootKeys.Subrooms), newSubId);
|
|
||||||
}
|
|
||||||
|
|
||||||
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, details.Room.Name), details.Room.RoomId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async cloneRoom(roomid: number, newname: string, newowner: Profile) {
|
async cloneRoom(roomid: number, newname: string, newowner: Profile) {
|
||||||
const canBeClonedRaw = await Redis.Database.hget(Redis.buildKey(
|
enum CloneResult {
|
||||||
Redis.KeyGroups.Rooms.Root,
|
Success,
|
||||||
roomid.toString(),
|
DoesNotAllowCloning,
|
||||||
Rooms.roomRootKeys.Meta
|
CannotCloneDormRoom,
|
||||||
), Rooms.roomMetaKeys.CloningAllowed);
|
NameIsTaken,
|
||||||
if (!canBeClonedRaw) return null;
|
Unknown
|
||||||
let canBeCloned = null;
|
|
||||||
try {
|
|
||||||
canBeCloned = JSON.parse(canBeClonedRaw) as boolean;
|
|
||||||
} catch {
|
|
||||||
log.d(`Cloneroom ${roomid}: parse error`);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
if (!canBeCloned) {
|
interface RoomClone {
|
||||||
log.d(`Cloneroom ${roomid}: cannot be cloned`);
|
factory?: RoomFactory;
|
||||||
return null;
|
result: CloneResult;
|
||||||
}
|
}
|
||||||
const beforeRoom = await Rooms.get(roomid); // room must exist
|
|
||||||
if (!beforeRoom || !beforeRoom.Room.CloningAllowed) return null; // room must be cloneable
|
const factory = await new RoomFactory({ id: roomid, factoryMode: FactoryMode.Fetch }).init();
|
||||||
if (beforeRoom.Room.Name !== 'DormRoom' && await Rooms.getByName(newname)) return null; // room name cannot be taken
|
if (!factory || !factory.CloningAllowed) return { result: CloneResult.DoesNotAllowCloning } as RoomClone;
|
||||||
|
if (factory.Name == 'DormRoom') return { result: CloneResult.CannotCloneDormRoom } as RoomClone;
|
||||||
const newId = await this.#getAvailableRoomId();
|
if (factory.Name == newname) return { result: CloneResult.NameIsTaken } as RoomClone;
|
||||||
beforeRoom.Room.CreatorPlayerId = newowner.getId();
|
|
||||||
beforeRoom.Room.RoomId = newId;
|
|
||||||
for (const subroom of beforeRoom.Scenes) subroom.RoomId = newId;
|
|
||||||
|
|
||||||
await Rooms.#setRoom(beforeRoom);
|
|
||||||
return beforeRoom;
|
|
||||||
|
|
||||||
|
const newFactory = await new RoomFactory({ id: await Rooms.#getAvailableRoomId(), factoryMode: FactoryMode.Write }).init();
|
||||||
|
if (!newFactory) return { result: CloneResult.Unknown } as RoomClone;
|
||||||
|
|
||||||
|
newFactory.CreatorPlayerId = newowner.getId();
|
||||||
|
newFactory.Description = factory.Description;
|
||||||
|
newFactory.Name = factory.Name;
|
||||||
|
newFactory.ImageName = factory.Description;
|
||||||
|
newFactory.State = factory.State;
|
||||||
|
newFactory.RoomAccessibility = factory.RoomAccessibility;
|
||||||
|
newFactory.SupportsLevelVoting = factory.SupportsLevelVoting;
|
||||||
|
newFactory.IsAGRoom = factory.IsAGRoom;
|
||||||
|
newFactory.IsDormRoom = factory.IsDormRoom;
|
||||||
|
newFactory.CloningAllowed = false; // new rooms cannot be cloned
|
||||||
|
newFactory.AllowsJuniors = factory.AllowsJuniors;
|
||||||
|
newFactory.RoomWarningMask = factory.RoomWarningMask;
|
||||||
|
newFactory.CustomRoomWarning = factory.CustomRoomWarning;
|
||||||
|
newFactory.DisableMicAutoMute = factory.DisableMicAutoMute;
|
||||||
|
|
||||||
|
const oldHardware = await factory.getHardwareSupport();
|
||||||
|
const hardwarePromises = oldHardware.map(hw => newFactory.addHardwareSupport(hw));
|
||||||
|
await Promise.all(hardwarePromises);
|
||||||
|
|
||||||
|
const oldSubroomIds = await factory.getAllSubroomIds();
|
||||||
|
const promises = oldSubroomIds.map(async (id) => {
|
||||||
|
const newSubroomFactory = newFactory.getSubroom(id, FactoryMode.Write, WriteMode.Overwrite);
|
||||||
|
const oldSubroomFactory = factory.getSubroom(id, FactoryMode.Fetch);
|
||||||
|
|
||||||
|
newSubroomFactory.RoomSceneLocationId = oldSubroomFactory.RoomSceneLocationId;
|
||||||
|
newSubroomFactory.Name = oldSubroomFactory.Name;
|
||||||
|
newSubroomFactory.IsSandbox = oldSubroomFactory.IsSandbox;
|
||||||
|
newSubroomFactory.DataBlobName = oldSubroomFactory.DataBlobName;
|
||||||
|
newSubroomFactory.MaxPlayers = oldSubroomFactory.MaxPlayers;
|
||||||
|
newSubroomFactory.CanMatchmakeInto = oldSubroomFactory.CanMatchmakeInto;
|
||||||
|
|
||||||
|
await newSubroomFactory.write();
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
await newFactory.write();
|
||||||
|
|
||||||
|
return {
|
||||||
|
factory: newFactory,
|
||||||
|
result: CloneResult.Success
|
||||||
|
} as RoomClone
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProfileDormDefault(player: Profile) {
|
async getProfileDormDefault(player: Profile) {
|
||||||
@@ -209,131 +155,127 @@ class RoomsBase {
|
|||||||
Redis.KeyGroups.Rooms.Root,
|
Redis.KeyGroups.Rooms.Root,
|
||||||
Redis.KeyGroups.Rooms.PlayerDorms
|
Redis.KeyGroups.Rooms.PlayerDorms
|
||||||
), player.getId().toString());
|
), player.getId().toString());
|
||||||
|
|
||||||
if (unparsedId) {
|
if (unparsedId) {
|
||||||
log.d(`Unparsed dorm ID for profile ${player.getId()}: ${unparsedId}`);
|
log.d(`Unparsed dorm ID for profile ${player.getId()}: ${unparsedId}`);
|
||||||
const parsedId = parseInt(unparsedId);
|
const parsedId = parseInt(unparsedId);
|
||||||
if (isNaN(parsedId)) {
|
if (!isNaN(parsedId)) {
|
||||||
log.d(`Returning new dorm for profile ${player.getId()}`);
|
log.d(`Returning existing dorm for profile ${player.getId()}`);
|
||||||
return await Rooms.get(parsedId);
|
return await Rooms.get(parsedId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newDorm = await this.generateNewDorm(player);
|
const newDorm = await this.generateNewDorm(player);
|
||||||
await this.#setRoom(newDorm);
|
|
||||||
log.d(`New dorm for ${player.getId()} existed`);
|
log.d(`New dorm for ${player.getId()} existed`);
|
||||||
if (!newDorm) return null;
|
if (!newDorm) return null;
|
||||||
log.d(`New dorm for ${player.getId()} was not null`);
|
log.d(`New dorm for ${player.getId()} was not null`);
|
||||||
|
|
||||||
await Redis.Database.hset(Redis.buildKey(
|
await Redis.Database.hset(Redis.buildKey(
|
||||||
Redis.KeyGroups.Rooms.Root,
|
Redis.KeyGroups.Rooms.Root,
|
||||||
Redis.KeyGroups.Rooms.PlayerDorms
|
Redis.KeyGroups.Rooms.PlayerDorms
|
||||||
), player.getId().toString(), newDorm.Room.RoomId);
|
), player.getId().toString(), newDorm.RoomId);
|
||||||
return newDorm;
|
|
||||||
|
return await newDorm.export();
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateNewDorm(player: Profile) {
|
async generateNewDorm(player: Profile) {
|
||||||
const id = await this.#getAvailableRoomId();
|
const id = await this.#getAvailableRoomId();
|
||||||
const basedorm: RoomDetails = {
|
|
||||||
Room: {
|
const factory = await new RoomFactory({ id: id, factoryMode: FactoryMode.Write, writeMode: WriteMode.WriteIfFree }).init();
|
||||||
RoomId: id,
|
if (!factory) return null;
|
||||||
Name: `DormRoom`,
|
|
||||||
Description: "Your private room.",
|
factory.Name = "DormRoom";
|
||||||
CreatorPlayerId: player.getId(),
|
factory.Description = "Your private room.";
|
||||||
ImageName: "DefaultProfileImage.png",
|
factory.CreatorPlayerId = player.getId();
|
||||||
State: RoomState.Active,
|
factory.ImageName = "DefaultProfileImage.png";
|
||||||
Accessibility: RoomAccessibility.Private,
|
factory.State = RoomState.Active;
|
||||||
SupportsLevelVoting: false,
|
factory.RoomAccessibility = RoomAccessibility.Private;
|
||||||
IsAGRoom: false,
|
factory.SupportsLevelVoting = false;
|
||||||
IsDormRoom: true,
|
factory.IsAGRoom = false;
|
||||||
CloningAllowed: false,
|
factory.IsDormRoom = true;
|
||||||
SupportsScreens: true,
|
factory.CloningAllowed = false;
|
||||||
SupportsTeleportVR: true,
|
factory.AllowsJuniors = true;
|
||||||
SupportsWalkVR: true,
|
factory.RoomWarningMask = 0;
|
||||||
AllowsJuniors: true,
|
factory.CustomRoomWarning = "";
|
||||||
RoomWarningMask: 0,
|
|
||||||
CustomRoomWarning: "",
|
factory.addHardwareSupport('*');
|
||||||
DisableMicAutoMute: false
|
|
||||||
},
|
const subroomFactory = factory.getSubroom(await this.#getAvailableSubRoomId(id), FactoryMode.Write, WriteMode.WriteIfFree);
|
||||||
Scenes: [
|
if (!subroomFactory) return null;
|
||||||
{
|
|
||||||
RoomId: id,
|
subroomFactory.RoomSceneLocationId = IntegratedRoomScene.DormRoom;
|
||||||
RoomSceneId: 1,
|
subroomFactory.Name = "Home";
|
||||||
Name: "Home",
|
subroomFactory.IsSandbox = true;
|
||||||
RoomSceneLocationId: "76d98498-60a1-430c-ab76-b54a29b7a163",
|
subroomFactory.DataBlobName = "";
|
||||||
IsSandbox: true,
|
subroomFactory.MaxPlayers = 4;
|
||||||
CanMatchmakeInto: true,
|
subroomFactory.CanMatchmakeInto = true;
|
||||||
MaxPlayers: 4,
|
|
||||||
DataBlobName: "",
|
factory.write();
|
||||||
DataModifiedAt: new Date().toISOString()
|
subroomFactory.write();
|
||||||
}
|
|
||||||
],
|
return factory;
|
||||||
CoOwners: [],
|
|
||||||
InvitedCoOwners: [],
|
|
||||||
Hosts: [],
|
|
||||||
InvitedHosts: [],
|
|
||||||
CheerCount: 0,
|
|
||||||
VisitCount: 0,
|
|
||||||
FavoriteCount: 0,
|
|
||||||
Tags: []
|
|
||||||
}
|
|
||||||
return basedorm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateBuiltinRooms() {
|
async generateBuiltinRooms() {
|
||||||
|
|
||||||
if ((await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.BuiltinGenerated))) !== null) return true;
|
if ((await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.BuiltinGenerated))) !== null) return true;
|
||||||
for (const builtinRoom of rooms) {
|
await Promise.all(rooms.map(async builtinRoom => {
|
||||||
if (builtinRoom.Name == 'DormRoom') continue;
|
if (builtinRoom.Name == 'DormRoom') return;
|
||||||
const newId = await this.#getAvailableRoomId();
|
const newId = await this.#getAvailableRoomId();
|
||||||
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.AGRooms), newId);
|
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.AGRooms), newId);
|
||||||
const roomDets: RoomDetails = {
|
|
||||||
Room: {
|
|
||||||
Name: builtinRoom.Name,
|
|
||||||
RoomId: newId,
|
|
||||||
Description: builtinRoom.Description,
|
|
||||||
CreatorPlayerId: 1,
|
|
||||||
ImageName: `${builtinRoom.Name}.png`,
|
|
||||||
State: RoomState.Active,
|
|
||||||
Accessibility: builtinRoom.Accessibility,
|
|
||||||
SupportsLevelVoting: builtinRoom.SupportsLevelVoting,
|
|
||||||
IsAGRoom: true,
|
|
||||||
IsDormRoom: builtinRoom.Name == 'DormRoom',
|
|
||||||
CloningAllowed: builtinRoom.Name == 'DormRoom' ? true : builtinRoom.CloningAllowed,
|
|
||||||
SupportsScreens: builtinRoom.SupportsScreens,
|
|
||||||
SupportsWalkVR: builtinRoom.SupportsWalkVR,
|
|
||||||
SupportsTeleportVR: builtinRoom.SupportsTeleportVR,
|
|
||||||
AllowsJuniors: true,
|
|
||||||
RoomWarningMask: 0,
|
|
||||||
CustomRoomWarning: ""
|
|
||||||
},
|
|
||||||
Scenes: [],
|
|
||||||
CoOwners: [],
|
|
||||||
InvitedCoOwners: [],
|
|
||||||
Hosts: [],
|
|
||||||
InvitedHosts: [],
|
|
||||||
CheerCount: 0,
|
|
||||||
FavoriteCount: 0,
|
|
||||||
VisitCount: 0,
|
|
||||||
Tags: []
|
|
||||||
}
|
|
||||||
for (const subroom of builtinRoom.Scenes) {
|
|
||||||
const newSubroomId = await this.#getAvailableSubRoomId(newId);
|
|
||||||
roomDets.Scenes.push({
|
|
||||||
RoomSceneId: newSubroomId,
|
|
||||||
RoomId: newId,
|
|
||||||
RoomSceneLocationId: subroom.RoomSceneLocationId,
|
|
||||||
Name: subroom.Name,
|
|
||||||
IsSandbox: subroom.IsSandbox,
|
|
||||||
DataBlobName: "",
|
|
||||||
MaxPlayers: subroom.MaxPlayers,
|
|
||||||
CanMatchmakeInto: subroom.CanMatchmakeInto ? subroom.CanMatchmakeInto : true,
|
|
||||||
DataModifiedAt: new Date().toISOString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.#setRoom(roomDets);
|
|
||||||
}
|
|
||||||
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.BuiltinGenerated), "1");
|
|
||||||
return false;
|
|
||||||
|
|
||||||
|
const factory = await new RoomFactory({ id: newId, factoryMode: FactoryMode.Write, writeMode: WriteMode.Overwrite }).init();
|
||||||
|
if (!factory) return;
|
||||||
|
|
||||||
|
factory.Name = builtinRoom.Name;
|
||||||
|
factory.Description = builtinRoom.Description;
|
||||||
|
factory.CreatorPlayerId = 1;
|
||||||
|
|
||||||
|
const baseImageChanges = [
|
||||||
|
{ room: "DodgeballVR", image: "Dodgeball" },
|
||||||
|
{ room: "PaintballVR", image: "Paintball" },
|
||||||
|
{ room: "StuntRunnerBaseRoom", image: "StuntRunner" },
|
||||||
|
{ room: "BowlingAlley", image: "Bowling" },
|
||||||
|
]
|
||||||
|
|
||||||
|
if (baseImageChanges.find(change => change.room == builtinRoom.Name)) {
|
||||||
|
const image = baseImageChanges.find(change => change.room == builtinRoom.Name)!;
|
||||||
|
factory.ImageName = `${image.image}.png`;
|
||||||
|
}
|
||||||
|
else factory.ImageName = `${builtinRoom.Name}.png`;
|
||||||
|
|
||||||
|
factory.State = RoomState.Active;
|
||||||
|
factory.RoomAccessibility = builtinRoom.Accessibility;
|
||||||
|
factory.SupportsLevelVoting = builtinRoom.SupportsLevelVoting;
|
||||||
|
factory.IsAGRoom = true;
|
||||||
|
factory.CloningAllowed = builtinRoom.CloningAllowed;
|
||||||
|
factory.AllowsJuniors = true;
|
||||||
|
factory.RoomWarningMask = 0;
|
||||||
|
factory.CustomRoomWarning = "";
|
||||||
|
|
||||||
|
if (builtinRoom.SupportsScreens) factory.addHardwareSupport('screens');
|
||||||
|
if (builtinRoom.SupportsTeleportVR) factory.addHardwareSupport('teleport_vr');
|
||||||
|
if (builtinRoom.SupportsWalkVR) factory.addHardwareSupport('walk_vr');
|
||||||
|
|
||||||
|
await Promise.all(builtinRoom.Scenes.map(async subroom => {
|
||||||
|
const newSubroomId = await this.#getAvailableSubRoomId(newId);
|
||||||
|
const subroomFactory = await factory.getSubroom(newSubroomId, FactoryMode.Write, WriteMode.Overwrite).init();
|
||||||
|
if (!subroomFactory) return;
|
||||||
|
|
||||||
|
subroomFactory.RoomSceneLocationId = subroom.RoomSceneLocationId;
|
||||||
|
subroomFactory.Name = subroom.Name;
|
||||||
|
subroomFactory.IsSandbox = subroom.IsSandbox;
|
||||||
|
subroomFactory.DataBlobName = "";
|
||||||
|
subroomFactory.MaxPlayers = subroom.MaxPlayers;
|
||||||
|
subroomFactory.CanMatchmakeInto = subroom.CanMatchmakeInto;
|
||||||
|
|
||||||
|
await subroomFactory.write();
|
||||||
|
}));
|
||||||
|
|
||||||
|
await factory.write();
|
||||||
|
}));
|
||||||
|
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Rooms.Root, RoomsBase.Keys.BuiltinGenerated), "1");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIdFromName(name: string) {
|
async getIdFromName(name: string) {
|
||||||
@@ -344,23 +286,13 @@ class RoomsBase {
|
|||||||
return parsedId;
|
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): Promise<string[]>;
|
||||||
async getSubroomIdsFromRoom(id: number, stringify: false): Promise<number[]>;
|
async getSubroomIdsFromRoom(id: number, stringify: false): Promise<number[]>;
|
||||||
async getSubroomIdsFromRoom(id: number, stringify: boolean | undefined = false): Promise<string[] | number[]> {
|
async getSubroomIdsFromRoom(id: number, stringify: boolean | undefined = false): Promise<string[] | number[]> {
|
||||||
const ids = await Redis.Database.smembers(Redis.buildKey(
|
const ids = await Redis.Database.smembers(Redis.buildKey(
|
||||||
Redis.KeyGroups.Rooms.Root,
|
Redis.KeyGroups.Rooms.Root,
|
||||||
id.toString(),
|
id.toString(),
|
||||||
this.roomRootKeys.Subrooms
|
RoomFactory.Keys.Subrooms
|
||||||
));
|
));
|
||||||
const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
|
const parsedIds = ids.map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||||
|
|
||||||
|
|||||||
@@ -15,54 +15,19 @@ GNU Affero General Public License for more details.
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
export enum IntegratedRoomScene {
|
export enum WriteMode {
|
||||||
Calibration = "f5fbd9c9-e853-4036-9d48-5f68e861af04",
|
Overwrite = "overwrite",
|
||||||
DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163",
|
WriteIfFree = "if_free"
|
||||||
RecCenter = "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
|
||||||
ThreeDCharades = "4078dfed-24bb-4db7-863f-578ba48d726b",
|
|
||||||
DiscGolfLake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
|
||||||
DiscGolfPropulsion = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
|
||||||
Dodgeball = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
|
||||||
Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499",
|
|
||||||
Paintball_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
|
||||||
Paintball_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
|
||||||
Paintball_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
|
||||||
Paintball_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
|
||||||
Paintball_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
|
||||||
PaintballVR_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
|
||||||
PaintballVR_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
|
||||||
PaintballVR_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
|
||||||
PaintballVR_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
|
||||||
PaintballVR_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
|
||||||
GoldenTrophy = "91e16e35-f48f-4700-ab8a-a1b79e50e51b",
|
|
||||||
TheRiseofJumbotron = "acc06e66-c2d0-4361-b0cd-46246a4c455c",
|
|
||||||
CrimsonCauldron = "949fa41f-4347-45c0-b7ac-489129174045",
|
|
||||||
IsleOfLostSkulls = "7e01cfe0-820a-406f-b1b3-0a5bf575235c",
|
|
||||||
Soccer = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
|
||||||
LaserTagHangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
|
||||||
LaserTagCyberJunk = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
|
||||||
RecRoyaleSquads = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
|
||||||
RecRoyaleVR = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
|
||||||
RecRoyaleSolos = "b010171f-4875-4e89-baba-61e878cd41e1",
|
|
||||||
Lounge = "a067557f-ca32-43e6-b6e5-daaec60b4f5a",
|
|
||||||
PerformanceHall = "9932f88f-3929-43a0-a012-a40b5128e346",
|
|
||||||
MakerRoom = "a75f7547-79eb-47c6-8986-6767abcb4f92",
|
|
||||||
Park = "0a864c86-5a71-4e18-8041-8124e4dc9d98",
|
|
||||||
ArtTesting = "42699ed2-0c1b-4f3d-93a2-ce01dfce7a79",
|
|
||||||
River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
|
||||||
Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
|
||||||
Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
|
||||||
Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
|
||||||
Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
|
||||||
Lake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
|
||||||
PropulsionTestRange = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
|
||||||
Gym = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
|
||||||
Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
|
||||||
Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
|
||||||
CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
|
||||||
DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum FactoryMode {
|
||||||
|
Fetch = 'fetch',
|
||||||
|
Write = 'write'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HardwareSupport = "screens" | "walk_vr" | "teleport_vr" | "low_vr" | "mobile";
|
||||||
|
export const HardwareSupportStrings = ["screens", "walk_vr", "teleport_vr", "low_vr", "mobile"];
|
||||||
|
|
||||||
export enum RoomState {
|
export enum RoomState {
|
||||||
Active,
|
Active,
|
||||||
PendingJunior = 11,
|
PendingJunior = 11,
|
||||||
@@ -72,9 +37,9 @@ export enum RoomState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum RoomAccessibility {
|
export enum RoomAccessibility {
|
||||||
Private,
|
Private,
|
||||||
Public,
|
Public,
|
||||||
Unlisted
|
Unlisted
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuiltinScene {
|
export interface BuiltinScene {
|
||||||
@@ -131,7 +96,7 @@ export interface Room {
|
|||||||
AllowsJuniors: boolean,
|
AllowsJuniors: boolean,
|
||||||
RoomWarningMask: number, // generated by dedicated mask generation function
|
RoomWarningMask: number, // generated by dedicated mask generation function
|
||||||
CustomRoomWarning: string,
|
CustomRoomWarning: string,
|
||||||
DisableMicAutoMute?: boolean
|
DisableMicAutoMute?: boolean | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RoomWarningMask {
|
export enum RoomWarningMask {
|
||||||
@@ -169,4 +134,58 @@ export interface RoomDetails {
|
|||||||
FavoriteCount: number,
|
FavoriteCount: number,
|
||||||
VisitCount: number,
|
VisitCount: number,
|
||||||
Tags: TagDTO[]
|
Tags: TagDTO[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum IntegratedRoomScene {
|
||||||
|
DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163",
|
||||||
|
RecCenter = "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
||||||
|
ThreeDCharades = "4078dfed-24bb-4db7-863f-578ba48d726b",
|
||||||
|
DiscGolfLake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||||
|
DiscGolfPropulsion = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||||
|
Dodgeball = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||||
|
Paddleball = "d89f74fa-d51e-477a-a425-025a891dd499",
|
||||||
|
Paintball_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||||
|
Paintball_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||||
|
Paintball_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||||
|
Paintball_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||||
|
Paintball_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||||
|
Paintball_DriveIn = "65ddbb48-5a01-4e3e-972d-e5c7419e2bc3",
|
||||||
|
PaintballVR_River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||||
|
PaintballVR_Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||||
|
PaintballVR_Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||||
|
PaintballVR_Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||||
|
PaintballVR_Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||||
|
GoldenTrophy = "91e16e35-f48f-4700-ab8a-a1b79e50e51b",
|
||||||
|
TheRiseofJumbotron = "acc06e66-c2d0-4361-b0cd-46246a4c455c",
|
||||||
|
CrimsonCauldron = "949fa41f-4347-45c0-b7ac-489129174045",
|
||||||
|
IsleOfLostSkulls = "7e01cfe0-820a-406f-b1b3-0a5bf575235c",
|
||||||
|
Soccer = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||||
|
LaserTag_Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||||
|
LaserTag_CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||||
|
RecRoyaleSquads = "253fa009-6e65-4c90-91a1-7137a56a267f",
|
||||||
|
RecRoyaleSolos = "b010171f-4875-4e89-baba-61e878cd41e1",
|
||||||
|
Lounge = "a067557f-ca32-43e6-b6e5-daaec60b4f5a",
|
||||||
|
PerformanceHall = "9932f88f-3929-43a0-a012-a40b5128e346",
|
||||||
|
MakerRoom = "a75f7547-79eb-47c6-8986-6767abcb4f92",
|
||||||
|
Park = "0a864c86-5a71-4e18-8041-8124e4dc9d98",
|
||||||
|
River = "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
||||||
|
Homestead = "a785267d-c579-42ea-be43-fec1992d1ca7",
|
||||||
|
Quarry = "ff4c6427-7079-4f59-b22a-69b089420827",
|
||||||
|
Clearcut = "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
||||||
|
Spillway = "58763055-2dfb-4814-80b8-16fac5c85709",
|
||||||
|
Lake = "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
||||||
|
PropulsionTestRange = "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
||||||
|
Gym = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||||
|
Stadium = "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
||||||
|
Hangar = "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
||||||
|
CyberJunkCity = "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
||||||
|
DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
||||||
|
Crescendo = "49cb8993-a956-43e2-86f4-1318f279b22a",
|
||||||
|
Bowling = "ae929543-9a07-41d5-8ee9-dbbee8c36800",
|
||||||
|
BowlingAlley = "ae929543-9a07-41d5-8ee9-dbbee8c36800",
|
||||||
|
StuntRunner_StuntRunner = "b7281665-a715-4051-826b-8e08e69c6172",
|
||||||
|
StuntRunner_TheMainEvent = "3a636bd2-f896-424c-9225-c184522c0d87",
|
||||||
|
StuntRunnerBaseRoom = "882e9b96-7115-4b03-86f6-c0c9d8e22e00",
|
||||||
|
}
|
||||||
|
|
||||||
|
export * as RoomDataTypes from "./DataTypes.ts";
|
||||||
359
src/data/content/rooms/RoomFactory.ts
Normal file
359
src/data/content/rooms/RoomFactory.ts
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
/* 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 { FactoryMode, HardwareSupport, HardwareSupportStrings, RoomAccessibility, RoomDataTypes, RoomDetails, RoomState, WriteMode } from "./DataTypes.ts";
|
||||||
|
import { SubroomFactory } from "./SubroomFactory.ts";
|
||||||
|
|
||||||
|
interface RoomFactoryOptions {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
writeMode?: RoomDataTypes.WriteMode;
|
||||||
|
factoryMode?: RoomDataTypes.FactoryMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RoomFactory {
|
||||||
|
|
||||||
|
static Keys = {
|
||||||
|
Meta: "roommeta",
|
||||||
|
Subrooms: "subrooms",
|
||||||
|
VisitCount: "visits",
|
||||||
|
HardwareSupport: "hardware"
|
||||||
|
}
|
||||||
|
|
||||||
|
#cannotFetchDormError = new Error("Cannot fetch the name 'DormRoom'");
|
||||||
|
#mustSpecifyEitherIdOrNameError = new Error("A room name or room ID must be specified");
|
||||||
|
#mustSpecifyIdInWriteModeError = new Error("A room ID must be specified in fetch mode");
|
||||||
|
#mustFetchRoomFirstError = new Error("Cannot get room data before fetching");
|
||||||
|
#cannotWriteInFetchModeError = new Error("Cannot write to database in fetch mode");
|
||||||
|
#cannotWriteToUnresolvedIdError = new Error("Cannot write to an unresolved room ID");
|
||||||
|
#roomAlreadyExistsError = new Error("Room already exists. Use overwrite mode to overwrite");
|
||||||
|
#hashValuesNotSetError = new Error("Room meta values not set");
|
||||||
|
#unresolvedError = new Error("Cannot get subroom of roomId that was not resolved. Did you call init()?");
|
||||||
|
|
||||||
|
#specifiedId?: number;
|
||||||
|
#specifiedName?: string;
|
||||||
|
|
||||||
|
#writeMode: RoomDataTypes.WriteMode = RoomDataTypes.WriteMode.WriteIfFree;
|
||||||
|
factoryMode: RoomDataTypes.FactoryMode = RoomDataTypes.FactoryMode.Fetch;
|
||||||
|
|
||||||
|
#resolvedId: number | null = null;
|
||||||
|
|
||||||
|
#hash: Record<string, string> | null = null;
|
||||||
|
|
||||||
|
constructor(options: RoomFactoryOptions) {
|
||||||
|
|
||||||
|
if (options.name == 'DormRoom') throw this.#cannotFetchDormError;
|
||||||
|
this.#specifiedId = options.id;
|
||||||
|
this.#specifiedName = options.name;
|
||||||
|
if (options.writeMode) this.#writeMode = options.writeMode;
|
||||||
|
if (options.factoryMode) this.factoryMode = options.factoryMode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
|
||||||
|
if (this.factoryMode !== FactoryMode.Fetch) {
|
||||||
|
if (!this.#specifiedId) throw this.#mustSpecifyIdInWriteModeError;
|
||||||
|
this.#resolvedId = this.#specifiedId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.#specifiedId) {
|
||||||
|
if (!this.#specifiedName) throw this.#mustSpecifyEitherIdOrNameError;
|
||||||
|
const id = await Rooms.getIdFromName(this.#specifiedName);
|
||||||
|
if (!id) return null;
|
||||||
|
this.#specifiedId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#resolvedId = this.#specifiedId;
|
||||||
|
|
||||||
|
this.#hash = await Redis.Database.hgetall(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.Meta
|
||||||
|
));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async write() {
|
||||||
|
|
||||||
|
const id = this.#resolvedId;
|
||||||
|
if (!id) throw this.#cannotWriteToUnresolvedIdError;
|
||||||
|
|
||||||
|
if (this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#cannotWriteInFetchModeError;
|
||||||
|
else {
|
||||||
|
|
||||||
|
const dbkey = Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
id.toString(),
|
||||||
|
RoomFactory.Keys.Meta
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.#writeMode == RoomDataTypes.WriteMode.WriteIfFree) {
|
||||||
|
const exists = await Redis.Database.exists(dbkey);
|
||||||
|
const nameExists = this.Name == 'DormRoom' ? 0 : await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Room_Names, this.Name));
|
||||||
|
if (exists >= 1 || nameExists >= 1) throw this.#roomAlreadyExistsError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.#hash) throw this.#hashValuesNotSetError;
|
||||||
|
await Redis.Database.hset(dbkey, this.#hash);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Name !== 'DormRoom') await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, this.Name), this.RoomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async export() {
|
||||||
|
const hardwareSupport = await this.getHardwareSupport();
|
||||||
|
const subroomIds = await this.getAllSubroomIds();
|
||||||
|
|
||||||
|
const subroomPromises = subroomIds.map(id => this.getSubroom(id).init());
|
||||||
|
const subrooms = (await Promise.all(subroomPromises)).map(subroom => subroom.export());
|
||||||
|
|
||||||
|
const details: RoomDetails = {
|
||||||
|
Room: {
|
||||||
|
RoomId: this.RoomId,
|
||||||
|
Name: this.Name,
|
||||||
|
Description: this.Description,
|
||||||
|
CreatorPlayerId: this.CreatorPlayerId,
|
||||||
|
ImageName: this.ImageName,
|
||||||
|
State: this.State,
|
||||||
|
Accessibility: this.RoomAccessibility,
|
||||||
|
SupportsLevelVoting: this.SupportsLevelVoting,
|
||||||
|
IsAGRoom: this.IsAGRoom,
|
||||||
|
IsDormRoom: this.IsDormRoom ? true : undefined,
|
||||||
|
CloningAllowed: this.CloningAllowed,
|
||||||
|
SupportsScreens: hardwareSupport.includes('screens'),
|
||||||
|
SupportsWalkVR: hardwareSupport.includes('walk_vr'),
|
||||||
|
SupportsTeleportVR: hardwareSupport.includes('teleport_vr'),
|
||||||
|
AllowsJuniors: this.AllowsJuniors,
|
||||||
|
RoomWarningMask: this.RoomWarningMask,
|
||||||
|
CustomRoomWarning: this.CustomRoomWarning,
|
||||||
|
DisableMicAutoMute: this.DisableMicAutoMute ? true : undefined
|
||||||
|
},
|
||||||
|
Scenes: subrooms,
|
||||||
|
CoOwners: [],
|
||||||
|
InvitedCoOwners: [],
|
||||||
|
Hosts: [],
|
||||||
|
InvitedHosts: [],
|
||||||
|
CheerCount: 0,
|
||||||
|
FavoriteCount: 0,
|
||||||
|
VisitCount: await this.getVisitCount(),
|
||||||
|
Tags: []
|
||||||
|
}
|
||||||
|
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSubroom(id: number, factoryMode?: FactoryMode, writeMode?: WriteMode) {
|
||||||
|
if (!this.#resolvedId) throw this.#unresolvedError;
|
||||||
|
return new SubroomFactory({
|
||||||
|
roomId: this.#resolvedId,
|
||||||
|
subroomId: id,
|
||||||
|
factoryMode: factoryMode ? factoryMode : undefined,
|
||||||
|
writeMode : writeMode ? writeMode : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllSubroomIds() {
|
||||||
|
if (!this.#resolvedId) throw this.#unresolvedError;
|
||||||
|
|
||||||
|
return (await Redis.Database.smembers(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.Subrooms
|
||||||
|
))).map(val => parseInt(val)).filter(val => !isNaN(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
#fetchStringKey(key: string, def: string) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchRoomFirstError;
|
||||||
|
else if (!this.#hash) return def;
|
||||||
|
return this.#hash[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
#fetchNumberKey(key: string, def: number) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchRoomFirstError;
|
||||||
|
else if (!this.#hash) return def;
|
||||||
|
const val = this.#hash[key];
|
||||||
|
if (isNaN(parseInt(val))) return def;
|
||||||
|
else return parseInt(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#fetchBooleanKey(key: string, def: boolean) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchRoomFirstError;
|
||||||
|
else if (!this.#hash) return def;
|
||||||
|
const val = this.#hash[key];
|
||||||
|
try {
|
||||||
|
return JSON.parse(val) as boolean;
|
||||||
|
} catch {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#setHashValue(key: string, value: string | number | boolean) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchRoomFirstError;
|
||||||
|
|
||||||
|
if (!this.#hash) this.#hash = {};
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
const val = JSON.stringify(value);
|
||||||
|
if (!val) throw new Error("Cannot stringify given value");
|
||||||
|
|
||||||
|
this.#hash[key] = val;
|
||||||
|
} else this.#hash[key] = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
get RoomId() { if (!this.#resolvedId) throw this.#unresolvedError; return this.#resolvedId; }
|
||||||
|
|
||||||
|
#nameKey = 'Name';
|
||||||
|
get Name() { return this.#fetchStringKey(this.#nameKey, 'DATABASEERROR') }
|
||||||
|
set Name(data) { this.#setHashValue(this.#nameKey, data) }
|
||||||
|
|
||||||
|
#descKey = 'Description';
|
||||||
|
get Description() { return this.#fetchStringKey(this.#descKey, 'Database Error. Contact an administrator.') }
|
||||||
|
set Description(data) { this.#setHashValue(this.#descKey, data) }
|
||||||
|
|
||||||
|
#creatorKey = 'CreatorPlayerId';
|
||||||
|
get CreatorPlayerId() { return this.#fetchNumberKey(this.#creatorKey, 1) }
|
||||||
|
set CreatorPlayerId(data) { this.#setHashValue(this.#creatorKey, data) }
|
||||||
|
|
||||||
|
#imageKey = 'ImageName';
|
||||||
|
get ImageName() { return this.#fetchStringKey(this.#imageKey, 'DefaultProfileImage.png') }
|
||||||
|
set ImageName(data) { this.#setHashValue(this.#imageKey, data) }
|
||||||
|
|
||||||
|
#stateKey = 'State';
|
||||||
|
get State(): RoomState { return this.#fetchNumberKey(this.#stateKey, RoomState.Active) }
|
||||||
|
set State(data) { this.#setHashValue(this.#stateKey, data) }
|
||||||
|
|
||||||
|
#accessKey = 'RoomAccessibility';
|
||||||
|
get RoomAccessibility(): RoomAccessibility { return this.#fetchNumberKey(this.#accessKey, RoomAccessibility.Unlisted) }
|
||||||
|
set RoomAccessibility(data) { this.#setHashValue(this.#accessKey, data) }
|
||||||
|
|
||||||
|
#votingKey = 'SupportsLevelVoting';
|
||||||
|
get SupportsLevelVoting() { return this.#fetchBooleanKey(this.#votingKey, false) }
|
||||||
|
set SupportsLevelVoting(data) { this.#setHashValue(this.#votingKey, data) }
|
||||||
|
|
||||||
|
#agroomKey = 'IsAGRoom';
|
||||||
|
get IsAGRoom() { return this.#fetchBooleanKey(this.#agroomKey, true) }
|
||||||
|
set IsAGRoom(data) { this.#setHashValue(this.#agroomKey, data) }
|
||||||
|
|
||||||
|
#dormKey = 'IsDormRoom';
|
||||||
|
get IsDormRoom() { return this.#fetchBooleanKey(this.#dormKey, false) }
|
||||||
|
set IsDormRoom(data) { this.#setHashValue(this.#dormKey, data) }
|
||||||
|
|
||||||
|
#cloningKey = 'CloningAllowed';
|
||||||
|
get CloningAllowed() { return this.#fetchBooleanKey(this.#cloningKey, false) }
|
||||||
|
set CloningAllowed(data) { this.#setHashValue(this.#cloningKey, data) }
|
||||||
|
|
||||||
|
#juniorsKey = 'AllowsJuniors';
|
||||||
|
get AllowsJuniors() { return this.#fetchBooleanKey(this.#juniorsKey, false) }
|
||||||
|
set AllowsJuniors(data) { this.#setHashValue(this.#juniorsKey, data) }
|
||||||
|
|
||||||
|
#maskKey = 'RoomWarningMask';
|
||||||
|
get RoomWarningMask() { return this.#fetchNumberKey(this.#maskKey, 0) }
|
||||||
|
set RoomWarningMask(data) { this.#setHashValue(this.#maskKey, data) }
|
||||||
|
|
||||||
|
#warnKey = 'CustomRoomWarning';
|
||||||
|
get CustomRoomWarning() { return this.#fetchStringKey(this.#warnKey, '') }
|
||||||
|
set CustomRoomWarning(data) { this.#setHashValue(this.#warnKey, data) }
|
||||||
|
|
||||||
|
#muteKey = 'DisableMicAutoMute';
|
||||||
|
get DisableMicAutoMute() { return this.#fetchBooleanKey(this.#muteKey, false) }
|
||||||
|
set DisableMicAutoMute(data) { this.#setHashValue(this.#muteKey, data) }
|
||||||
|
|
||||||
|
async getHardwareSupport(): Promise<HardwareSupport[]> {
|
||||||
|
if (!this.#resolvedId) throw this.#unresolvedError;
|
||||||
|
return (await Redis.Database.smembers(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.HardwareSupport
|
||||||
|
))) as HardwareSupport[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async addHardwareSupport(hardware: HardwareSupport | HardwareSupport[] | '*') {
|
||||||
|
if (!this.#resolvedId) throw this.#unresolvedError;
|
||||||
|
|
||||||
|
if (hardware === '*') {
|
||||||
|
await Promise.all(HardwareSupportStrings.map(str => this.addHardwareSupport(str as HardwareSupport) ));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof hardware == 'string') {
|
||||||
|
await Redis.Database.sadd(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.HardwareSupport
|
||||||
|
), hardware);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const promises = hardware.map(hw => Redis.Database.sadd(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId!.toString(),
|
||||||
|
RoomFactory.Keys.HardwareSupport
|
||||||
|
), hw));
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeHardwareSupport(hardware: HardwareSupport) {
|
||||||
|
if (!this.#resolvedId) throw this.#unresolvedError;
|
||||||
|
await Redis.Database.srem(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.HardwareSupport
|
||||||
|
), hardware);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVisitCount() {
|
||||||
|
if (!this.#resolvedId) throw this.#unresolvedError;
|
||||||
|
|
||||||
|
const visits = await Redis.Database.get(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.VisitCount
|
||||||
|
));
|
||||||
|
if (!visits) return 0;
|
||||||
|
const parsedVisits = parseInt(visits);
|
||||||
|
if (isNaN(parsedVisits)) return 0;
|
||||||
|
else return parsedVisits;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addVisit() {
|
||||||
|
if (!this.#resolvedId) throw this.#unresolvedError;
|
||||||
|
|
||||||
|
let visits = await Redis.Database.get(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.VisitCount
|
||||||
|
));
|
||||||
|
if (!visits) visits = "0";
|
||||||
|
let parsedVisits = parseInt(visits);
|
||||||
|
if (isNaN(parsedVisits)) parsedVisits = 0;
|
||||||
|
|
||||||
|
await Redis.Database.set(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#resolvedId.toString(),
|
||||||
|
RoomFactory.Keys.VisitCount
|
||||||
|
), parsedVisits + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
227
src/data/content/rooms/SubroomFactory.ts
Normal file
227
src/data/content/rooms/SubroomFactory.ts
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
/* 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 { RoomDataTypes, IntegratedRoomScene, RoomScene, WriteMode, FactoryMode } from "./DataTypes.ts";
|
||||||
|
import { RoomFactory } from "./RoomFactory.ts";
|
||||||
|
|
||||||
|
interface SubroomFactoryOptions {
|
||||||
|
roomId: number;
|
||||||
|
subroomId: number;
|
||||||
|
writeMode?: WriteMode;
|
||||||
|
factoryMode?: FactoryMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SubroomFactory {
|
||||||
|
|
||||||
|
static Keys = {
|
||||||
|
Meta: "meta",
|
||||||
|
Blobs: "blobs"
|
||||||
|
}
|
||||||
|
|
||||||
|
#mustFetchSubroomFirstError = new Error("Cannot get subroom data before fetching");
|
||||||
|
#unspecifiedArguments = new Error("A roomId and subroomId must be specified");
|
||||||
|
#cannotWriteInFetchModeError = new Error("Cannot write to database in fetch mode");
|
||||||
|
#subroomAlreadyExistsError = new Error("Subroom already exists. Use overwrite mode to overwrite");
|
||||||
|
#hashValuesNotSetError = new Error("Subroom meta values not set");
|
||||||
|
|
||||||
|
#writeMode: RoomDataTypes.WriteMode = RoomDataTypes.WriteMode.WriteIfFree;
|
||||||
|
factoryMode: RoomDataTypes.FactoryMode = RoomDataTypes.FactoryMode.Fetch;
|
||||||
|
|
||||||
|
#roomId: number;
|
||||||
|
#subroomId: number;
|
||||||
|
|
||||||
|
#hash: Record<string, string> | null = null;
|
||||||
|
|
||||||
|
constructor(options: SubroomFactoryOptions) {
|
||||||
|
this.#roomId = options.roomId;
|
||||||
|
this.#subroomId = options.subroomId;
|
||||||
|
if (options.writeMode) this.#writeMode = options.writeMode;
|
||||||
|
if (options.factoryMode) this.factoryMode = options.factoryMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
|
||||||
|
if (!this.#roomId || !this.#subroomId) throw this.#unspecifiedArguments;
|
||||||
|
|
||||||
|
this.#hash = await Redis.Database.hgetall(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#roomId.toString(),
|
||||||
|
RoomFactory.Keys.Subrooms,
|
||||||
|
this.#subroomId.toString(),
|
||||||
|
SubroomFactory.Keys.Meta
|
||||||
|
));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async write() {
|
||||||
|
|
||||||
|
if (this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#cannotWriteInFetchModeError;
|
||||||
|
else {
|
||||||
|
|
||||||
|
const dbkey = Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#roomId.toString(),
|
||||||
|
RoomFactory.Keys.Subrooms,
|
||||||
|
this.#subroomId.toString(),
|
||||||
|
SubroomFactory.Keys.Meta
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.#writeMode == RoomDataTypes.WriteMode.WriteIfFree) {
|
||||||
|
const exists = await Redis.Database.exists(dbkey);
|
||||||
|
if (exists >= 1) throw this.#subroomAlreadyExistsError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.#hash) throw this.#hashValuesNotSetError;
|
||||||
|
this.#hash['DataModifiedAt'] = new Date().toISOString();
|
||||||
|
|
||||||
|
await Redis.Database.hset(dbkey, this.#hash);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
await Redis.Database.sadd(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#roomId.toString(),
|
||||||
|
RoomFactory.Keys.Subrooms
|
||||||
|
), this.RoomSceneId);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export(): RoomScene {
|
||||||
|
return {
|
||||||
|
RoomSceneId: this.RoomSceneId,
|
||||||
|
RoomId: this.RoomId,
|
||||||
|
RoomSceneLocationId: this.RoomSceneLocationId,
|
||||||
|
Name: this.Name,
|
||||||
|
IsSandbox: this.IsSandbox,
|
||||||
|
DataBlobName: this.DataBlobName,
|
||||||
|
MaxPlayers: this.MaxPlayers,
|
||||||
|
CanMatchmakeInto: this.CanMatchmakeInto,
|
||||||
|
DataModifiedAt: this.DataModifiedAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#fetchStringKey(key: string, def: string) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchSubroomFirstError;
|
||||||
|
else if (!this.#hash) return def;
|
||||||
|
return this.#hash[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
#fetchNumberKey(key: string, def: number) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchSubroomFirstError;
|
||||||
|
else if (!this.#hash) return def;
|
||||||
|
const val = this.#hash[key];
|
||||||
|
if (isNaN(parseInt(val))) return def;
|
||||||
|
else return parseInt(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#fetchBooleanKey(key: string, def: boolean) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchSubroomFirstError;
|
||||||
|
else if (!this.#hash) return def;
|
||||||
|
const val = this.#hash[key];
|
||||||
|
try {
|
||||||
|
return JSON.parse(val) as boolean;
|
||||||
|
} catch {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#setHashValue(key: string, value: string | number | boolean) {
|
||||||
|
if (!this.#hash && this.factoryMode == RoomDataTypes.FactoryMode.Fetch) throw this.#mustFetchSubroomFirstError;
|
||||||
|
|
||||||
|
if (!this.#hash) this.#hash = {};
|
||||||
|
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
const val = JSON.stringify(value);
|
||||||
|
if (!val) throw new Error("Cannot stringify given value");
|
||||||
|
|
||||||
|
this.#hash[key] = val;
|
||||||
|
this.#hash[this.#modifiedKey] = new Date().toISOString();
|
||||||
|
} else this.#hash[key] = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
get RoomSceneId() { return this.#subroomId }
|
||||||
|
|
||||||
|
get RoomId() { return this.#roomId }
|
||||||
|
|
||||||
|
#locationKey = 'RoomSceneLocationId';
|
||||||
|
get RoomSceneLocationId(): IntegratedRoomScene { return this.#fetchStringKey(this.#locationKey, IntegratedRoomScene.PerformanceHall) as IntegratedRoomScene }
|
||||||
|
set RoomSceneLocationId(data) { this.#setHashValue(this.#locationKey, data) }
|
||||||
|
|
||||||
|
#nameKey = 'Name';
|
||||||
|
get Name() { return this.#fetchStringKey(this.#nameKey, "Home") }
|
||||||
|
set Name(data) { this.#setHashValue(this.#nameKey, data) }
|
||||||
|
|
||||||
|
#sandboxKey = 'IsSandbox';
|
||||||
|
get IsSandbox() { return this.#fetchBooleanKey(this.#sandboxKey, false) }
|
||||||
|
set IsSandbox(data) { this.#setHashValue(this.#sandboxKey, data) }
|
||||||
|
|
||||||
|
#blobKey = 'DataBlobName';
|
||||||
|
get DataBlobName() { return this.#fetchStringKey(this.#blobKey, "") }
|
||||||
|
set DataBlobName(data) { this.#setHashValue(this.#blobKey, data) }
|
||||||
|
|
||||||
|
#playersKey = 'MaxPlayers';
|
||||||
|
get MaxPlayers() { return this.#fetchNumberKey(this.#playersKey, 8) }
|
||||||
|
set MaxPlayers(data) { this.#setHashValue(this.#playersKey, data) }
|
||||||
|
|
||||||
|
#matchKey = 'CanMatchmakeInto';
|
||||||
|
get CanMatchmakeInto() { return this.#fetchBooleanKey(this.#matchKey, true) }
|
||||||
|
set CanMatchmakeInto(data) { this.#setHashValue(this.#matchKey, data) }
|
||||||
|
|
||||||
|
#modifiedKey = 'DataModifiedAt';
|
||||||
|
get DataModifiedAt() { return this.#fetchStringKey(this.#modifiedKey, new Date().toISOString()) }
|
||||||
|
|
||||||
|
async addBlobHistory(date: Date, filename: string) {
|
||||||
|
await Redis.Database.hset(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#roomId.toString(),
|
||||||
|
RoomFactory.Keys.Subrooms,
|
||||||
|
this.#subroomId.toString(),
|
||||||
|
SubroomFactory.Keys.Blobs
|
||||||
|
), date.toISOString(), filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBlobHistory() {
|
||||||
|
interface History {
|
||||||
|
time: Date,
|
||||||
|
filename: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const hist = await Redis.Database.hgetall(Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Rooms.Root,
|
||||||
|
this.#roomId.toString(),
|
||||||
|
RoomFactory.Keys.Subrooms,
|
||||||
|
this.#subroomId.toString(),
|
||||||
|
SubroomFactory.Keys.Blobs
|
||||||
|
));
|
||||||
|
|
||||||
|
return Object.keys(hist).map(key => {
|
||||||
|
const d = new Date(key);
|
||||||
|
if (d instanceof Date && !isNaN(d.getTime())) return null;
|
||||||
|
else {
|
||||||
|
const h: History = {
|
||||||
|
time: d,
|
||||||
|
filename: hist[key]
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}).filter(val => val !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ 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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
import Rooms from "../content/rooms.ts";
|
import Rooms from "../content/rooms.ts";
|
||||||
import { RoomAccessibility, RoomState } from "../content/roomtypes.ts";
|
import { RoomDataTypes } from "../content/rooms/DataTypes.ts";
|
||||||
import { Profile } from "../profiles.ts";
|
import { Profile } from "../profiles.ts";
|
||||||
import Instances from "./instances.ts";
|
import Instances from "./instances.ts";
|
||||||
import { MatchmakingErrorCode, RoomInstance } from "./types.ts";
|
import { MatchmakingErrorCode, RoomInstance } from "./types.ts";
|
||||||
@@ -77,9 +77,9 @@ class MatchmakingBase {
|
|||||||
// check to make sure room exists, is not private, and is active
|
// check to make sure room exists, is not private, and is active
|
||||||
const targetRoom = options.roomName !== 'DormRoom' ? await Rooms.getByName(options.roomName) : await Rooms.getProfileDormDefault(options.profile);
|
const targetRoom = options.roomName !== 'DormRoom' ? await Rooms.getByName(options.roomName) : await Rooms.getProfileDormDefault(options.profile);
|
||||||
if (!targetRoom) return { errorCode: MatchmakingErrorCode.NoSuchRoom };
|
if (!targetRoom) return { errorCode: MatchmakingErrorCode.NoSuchRoom };
|
||||||
if (targetRoom.Room.Accessibility == RoomAccessibility.Private && targetRoom.Room.CreatorPlayerId !== options.profile.getId())
|
if (targetRoom.Room.Accessibility == RoomDataTypes.RoomAccessibility.Private && targetRoom.Room.CreatorPlayerId !== options.profile.getId())
|
||||||
return { errorCode: MatchmakingErrorCode.RoomIsPrivate };
|
return { errorCode: MatchmakingErrorCode.RoomIsPrivate };
|
||||||
if (targetRoom.Room.State !== RoomState.Active) return { errorCode: MatchmakingErrorCode.RoomIsNotActive };
|
if (targetRoom.Room.State !== RoomDataTypes.RoomState.Active) return { errorCode: MatchmakingErrorCode.RoomIsNotActive };
|
||||||
const roomId = targetRoom.Room.RoomId;
|
const roomId = targetRoom.Room.RoomId;
|
||||||
|
|
||||||
Instances.clearAllRoomEmptyInstances(roomId);
|
Instances.clearAllRoomEmptyInstances(roomId);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ GNU Affero General Public License for more details.
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
import { RoomDetails } from "../content/roomtypes.ts";
|
import { RoomDataTypes } from "../content/rooms/DataTypes.ts";
|
||||||
import { Profile } from "../profiles.ts";
|
import { Profile } from "../profiles.ts";
|
||||||
|
|
||||||
export enum PhotonRegionCodeString {
|
export enum PhotonRegionCodeString {
|
||||||
@@ -70,7 +70,7 @@ export interface RoomInstance {
|
|||||||
|
|
||||||
export interface InstanceOptions {
|
export interface InstanceOptions {
|
||||||
|
|
||||||
Room: RoomDetails,
|
Room: RoomDataTypes.RoomDetails,
|
||||||
SceneIndex: number,
|
SceneIndex: number,
|
||||||
EventId?: number,
|
EventId?: number,
|
||||||
Name?: string,
|
Name?: string,
|
||||||
|
|||||||
@@ -24,8 +24,24 @@ const log = new Logging("ProfileProgression");
|
|||||||
|
|
||||||
const config = GameConfigs.getConfig();
|
const config = GameConfigs.getConfig();
|
||||||
|
|
||||||
|
interface PlayerProgressionExport {
|
||||||
|
PlayerId: number,
|
||||||
|
Level: number,
|
||||||
|
XP: number
|
||||||
|
}
|
||||||
|
|
||||||
export class ProfileProgressionManager extends ProfileContentManager {
|
export class ProfileProgressionManager extends ProfileContentManager {
|
||||||
|
|
||||||
|
async export() {
|
||||||
|
const ex: PlayerProgressionExport = {
|
||||||
|
PlayerId: this.profileId,
|
||||||
|
Level: await this.getLevel(),
|
||||||
|
XP: await this.getXp()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ex;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the profile's exact # of XP
|
* Set the profile's exact # of XP
|
||||||
* @returns The new # of XP
|
* @returns The new # of XP
|
||||||
@@ -54,7 +70,7 @@ export class ProfileProgressionManager extends ProfileContentManager {
|
|||||||
const xp = await this.getXp();
|
const xp = await this.getXp();
|
||||||
if (xp == null) return 1; // fallback since progression data is required
|
if (xp == null) return 1; // fallback since progression data is required
|
||||||
|
|
||||||
if (typeof config?.LevelProgressionMaps == 'undefined') return null;
|
if (typeof config?.LevelProgressionMaps == 'undefined') return 1;
|
||||||
for (const item of config?.LevelProgressionMaps) {
|
for (const item of config?.LevelProgressionMaps) {
|
||||||
if (xp >= item.RequiredXp) {
|
if (xp >= item.RequiredXp) {
|
||||||
|
|
||||||
@@ -64,6 +80,8 @@ export class ProfileProgressionManager extends ProfileContentManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 1; // fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
async getXp() {
|
async getXp() {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export class ProfileReputationManager extends ProfileContentManager {
|
|||||||
CheerCredit: 0,
|
CheerCredit: 0,
|
||||||
SubscriberCount: 0,
|
SubscriberCount: 0,
|
||||||
SubscribedCount: 0,
|
SubscribedCount: 0,
|
||||||
SelectedCheer: 0
|
SelectedCheer: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ export class ProfileSettingsManager extends ProfileContentManager {
|
|||||||
|
|
||||||
override async onProfileInit() {
|
override async onProfileInit() {
|
||||||
await this.setSetting(SettingKey.RecroomOOBE, SettingDefault.RecroomOOBE);
|
await this.setSetting(SettingKey.RecroomOOBE, SettingDefault.RecroomOOBE);
|
||||||
await this.setSetting(SettingKey.PlayerStatusVisibility, SettingDefault.PlayerStatusVisibility);
|
|
||||||
await this.setSetting(SettingKey.FIRST_TIME_IN_FLAGS, SettingDefault.FIRST_TIME_IN_FLAGS);
|
|
||||||
await this.setSetting(SettingKey.BACKPACK_FAVORITE_TOOL, SettingDefault.BACKPACK_FAVORITE_TOOL);
|
|
||||||
await this.setSetting(SettingKey.Recroom_ChallengeMap, SettingDefault.Recroom_ChallengeMap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSettings() {
|
async getSettings() {
|
||||||
|
|||||||
@@ -15,242 +15,27 @@ GNU Affero General Public License for more details.
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
|
// https://randomusernameapi.github.io/
|
||||||
const Dictionary = {
|
const Dictionary = {
|
||||||
Adjectives: [
|
Adjectives: [
|
||||||
"Amazing",
|
"Alpha", "Zen", "Ruby", "Pixel", "Captain",
|
||||||
"Affable",
|
"Luna", "Quantum", "Emerald", "Serene", "Sushi",
|
||||||
"Agreeable",
|
"Mountain", "Phoenix", "Electric", "Songbird", "Tech",
|
||||||
"Ambitious",
|
"Silver", "Midnight", "Tango", "Cosmic", "Jazz",
|
||||||
"Amicable",
|
"Velvet", "Neon", "Ghostly", "Ballet", "Delta",
|
||||||
"Animated",
|
"Echo", "Solar", "Pirate", "Harmonic",
|
||||||
"Astute",
|
"Cyber", "Melody", "Quasar", "Crimson", "Enigma",
|
||||||
"Authentic",
|
"Stardust", "Techno", "Lunar", "Rogue", "Dream"
|
||||||
"Blissful",
|
|
||||||
"Bold",
|
|
||||||
"Bright",
|
|
||||||
"Buoyant",
|
|
||||||
"Calm",
|
|
||||||
"Cheerful",
|
|
||||||
"Clever",
|
|
||||||
"Confident",
|
|
||||||
"Content",
|
|
||||||
"Creative",
|
|
||||||
"Cultured",
|
|
||||||
"Curious",
|
|
||||||
"Dashing",
|
|
||||||
"Dazzling",
|
|
||||||
"Dedicated",
|
|
||||||
"Diligent",
|
|
||||||
"Dynamic",
|
|
||||||
"Earnest",
|
|
||||||
"Easygoing",
|
|
||||||
"Ebullient",
|
|
||||||
"Endearing",
|
|
||||||
"Energetic",
|
|
||||||
"Engaging",
|
|
||||||
"Exuberant",
|
|
||||||
"Fantastic",
|
|
||||||
"Fearless",
|
|
||||||
"Fervent",
|
|
||||||
"Friendly",
|
|
||||||
"Funny",
|
|
||||||
"Generous",
|
|
||||||
"Gentle",
|
|
||||||
"Genuine",
|
|
||||||
"Gracious",
|
|
||||||
"Grateful",
|
|
||||||
"Helpful",
|
|
||||||
"Honest",
|
|
||||||
"Humble",
|
|
||||||
"Humorous",
|
|
||||||
"Incisive",
|
|
||||||
"Ingenious",
|
|
||||||
"Intuitive",
|
|
||||||
"Jovial",
|
|
||||||
"Jubilant",
|
|
||||||
"Just",
|
|
||||||
"Kind",
|
|
||||||
"Likable",
|
|
||||||
"Lively",
|
|
||||||
"Lovable",
|
|
||||||
"Loving",
|
|
||||||
"Loyal",
|
|
||||||
"Luminous",
|
|
||||||
"Magnetic",
|
|
||||||
"Marvelous",
|
|
||||||
"Masterful",
|
|
||||||
"Mature",
|
|
||||||
"Merciful",
|
|
||||||
"Mindful",
|
|
||||||
"Motivated",
|
|
||||||
"Natural",
|
|
||||||
"Nurturing",
|
|
||||||
"Observant",
|
|
||||||
"Outgoing",
|
|
||||||
"Patient",
|
|
||||||
"Peaceful",
|
|
||||||
"Placid",
|
|
||||||
"Playful",
|
|
||||||
"Pleasant",
|
|
||||||
"Poised",
|
|
||||||
"Positive",
|
|
||||||
"Powerful",
|
|
||||||
"Pragmatic",
|
|
||||||
"Proactive",
|
|
||||||
"Prudent",
|
|
||||||
"Punctual",
|
|
||||||
"Radiant",
|
|
||||||
"Rational",
|
|
||||||
"Real",
|
|
||||||
"Receptive",
|
|
||||||
"Reliable",
|
|
||||||
"Resilient",
|
|
||||||
"Robust",
|
|
||||||
"Sagacious",
|
|
||||||
"Serene",
|
|
||||||
"Sincere",
|
|
||||||
"Skillful",
|
|
||||||
"Smart",
|
|
||||||
"Sociable",
|
|
||||||
"Spirited",
|
|
||||||
"Splendid",
|
|
||||||
"Steady",
|
|
||||||
"Sterling",
|
|
||||||
"Strong",
|
|
||||||
"Sublime",
|
|
||||||
"Talented",
|
|
||||||
"Tenacious",
|
|
||||||
"Tireless",
|
|
||||||
"Tolerant",
|
|
||||||
"Tough",
|
|
||||||
"Tranquil",
|
|
||||||
"Unique",
|
|
||||||
"Upbeat",
|
|
||||||
"Valiant",
|
|
||||||
"Vibrant",
|
|
||||||
"Virtuous",
|
|
||||||
"Visionary",
|
|
||||||
"Vivacious",
|
|
||||||
"Welcoming",
|
|
||||||
"Wise",
|
|
||||||
"Witty",
|
|
||||||
"Wonderful",
|
|
||||||
"Zealous",
|
|
||||||
],
|
],
|
||||||
Nouns: [
|
Nouns: [
|
||||||
"Nomad",
|
"Wolf", "Master", "Red", "Pirate", "Adventure",
|
||||||
"Solstice",
|
"Lovegood", "Coder", "Enigma", "Seeker", "Samurai",
|
||||||
"Elysium",
|
"Mover", "Fire", "Echo", "Soul", "Titan",
|
||||||
"Horizon",
|
"Shadow", "Mystic", "Tornado", "Crafter", "Journey",
|
||||||
"Catalyst",
|
"Vortex", "Nebula", "Gazer", "Blossom", "Dynamo",
|
||||||
"Utopia",
|
"Eagle", "Symphony", "Willow", "Pioneer", "Hawk",
|
||||||
"Eclipse",
|
"Scribe", "Mistress", "Quest", "Comet", "Explorer",
|
||||||
"Nebula",
|
"Strider", "Trance", "Lullaby", "Dancer"
|
||||||
"Arcadia",
|
|
||||||
"Apex",
|
|
||||||
"Harmony",
|
|
||||||
"Zenith",
|
|
||||||
"Radiant",
|
|
||||||
"Infinity",
|
|
||||||
"Echo",
|
|
||||||
"Quasar",
|
|
||||||
"Cascade",
|
|
||||||
"Empyrean",
|
|
||||||
"Nebula",
|
|
||||||
"Odyssey",
|
|
||||||
"Aether",
|
|
||||||
"Empower",
|
|
||||||
"Zephyr",
|
|
||||||
"Vibrance",
|
|
||||||
"Astral",
|
|
||||||
"Jubilant",
|
|
||||||
"Zen",
|
|
||||||
"Nebulous",
|
|
||||||
"Ecliptic",
|
|
||||||
"Stellar",
|
|
||||||
"Quantum",
|
|
||||||
"Ethereal",
|
|
||||||
"Nexus",
|
|
||||||
"Synergy",
|
|
||||||
"Quantum",
|
|
||||||
"Enigma",
|
|
||||||
"Luminous",
|
|
||||||
"Epoch",
|
|
||||||
"Zenithal",
|
|
||||||
"Paragon",
|
|
||||||
"Panorama",
|
|
||||||
"Maverick",
|
|
||||||
"Voyager",
|
|
||||||
"Luminary",
|
|
||||||
"Catalyst",
|
|
||||||
"Phoenix",
|
|
||||||
"Dynamo",
|
|
||||||
"Zenith",
|
|
||||||
"Nexus",
|
|
||||||
"Pinnacle",
|
|
||||||
"Rhapsody",
|
|
||||||
"Serenity",
|
|
||||||
"Quantum",
|
|
||||||
"Apex",
|
|
||||||
"Harmony",
|
|
||||||
"Odyssey",
|
|
||||||
"Endeavor",
|
|
||||||
"Visionary",
|
|
||||||
"Epoch",
|
|
||||||
"Panache",
|
|
||||||
"Jubilee",
|
|
||||||
"Resonance",
|
|
||||||
"Zen",
|
|
||||||
"Nimbus",
|
|
||||||
"Ethereal",
|
|
||||||
"Cascade",
|
|
||||||
"Radiance",
|
|
||||||
"Nebula",
|
|
||||||
"Equinox",
|
|
||||||
"Pulsar",
|
|
||||||
"Apex",
|
|
||||||
"Ethos",
|
|
||||||
"Zenith",
|
|
||||||
"Nebula",
|
|
||||||
"Vertex",
|
|
||||||
"Equinox",
|
|
||||||
"Odyssey",
|
|
||||||
"Pantheon",
|
|
||||||
"Elysian",
|
|
||||||
"Nebulous",
|
|
||||||
"Quantum",
|
|
||||||
"Harmonic",
|
|
||||||
"Luminance",
|
|
||||||
"Paragon",
|
|
||||||
"Radiant",
|
|
||||||
"Epoch",
|
|
||||||
"Vortex",
|
|
||||||
"Celestia",
|
|
||||||
"Infinitum",
|
|
||||||
"Empyrean",
|
|
||||||
"Zephyr",
|
|
||||||
"Nimbus",
|
|
||||||
"Seraphic",
|
|
||||||
"Enigma",
|
|
||||||
"Synergy",
|
|
||||||
"Ecliptic",
|
|
||||||
"Utopian",
|
|
||||||
"Phoenix",
|
|
||||||
"Catalyst",
|
|
||||||
"Euphoria",
|
|
||||||
"Astral",
|
|
||||||
"Nebula",
|
|
||||||
"Ethereal",
|
|
||||||
"Zenith",
|
|
||||||
"Nexus",
|
|
||||||
"Empower",
|
|
||||||
"Panorama",
|
|
||||||
"Cascade",
|
|
||||||
"Quantum",
|
|
||||||
"Jubilant",
|
|
||||||
"Zen",
|
|
||||||
"Radiance",
|
|
||||||
"Labyrinth",
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ try {
|
|||||||
const authHeader = req.headers.get('authorization');
|
const authHeader = req.headers.get('authorization');
|
||||||
if (!authHeader) return { valid: false } as AuthResult;
|
if (!authHeader) return { valid: false } as AuthResult;
|
||||||
|
|
||||||
log.d(authHeader);
|
//log.d(authHeader);
|
||||||
const token = authHeader.split(", ")[1]; // Why is the header formatted like this?
|
const token = authHeader.split(", ")[1]; // Why is the header formatted like this?
|
||||||
if (!token) return { valid: false } as AuthResult;
|
if (!token) return { valid: false } as AuthResult;
|
||||||
const splitToken = token.split(' ')[1];
|
const splitToken = token.split(' ')[1];
|
||||||
@@ -220,6 +220,7 @@ try {
|
|||||||
if (!(await GameConfigs.getGameConfig('splitTestSoftOverrides'))) GameConfigs.setGameConfig('splitTestSoftOverrides', '');
|
if (!(await GameConfigs.getGameConfig('splitTestSoftOverrides'))) GameConfigs.setGameConfig('splitTestSoftOverrides', '');
|
||||||
if (!(await GameConfigs.getGameConfig('splitTestHardOverrides'))) GameConfigs.setGameConfig('splitTestHardOverrides', '');
|
if (!(await GameConfigs.getGameConfig('splitTestHardOverrides'))) GameConfigs.setGameConfig('splitTestHardOverrides', '');
|
||||||
|
|
||||||
|
log.i('Startup done.');
|
||||||
});
|
});
|
||||||
|
|
||||||
http.on('error', err => {
|
http.on('error', err => {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import { route as ImagesRoute } from "./api/images.ts";
|
|||||||
import { route as CommunityBoardRoute } from "./api/communityboard.ts";
|
import { route as CommunityBoardRoute } from "./api/communityboard.ts";
|
||||||
import { route as PlayerEventsRoute } from "./api/playerevents.ts";
|
import { route as PlayerEventsRoute } from "./api/playerevents.ts";
|
||||||
import { route as StorefrontsRoute } from "./api/storefronts.ts";
|
import { route as StorefrontsRoute } from "./api/storefronts.ts";
|
||||||
|
import { route as AnnouncementRoute } from "./api/announcement.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/api");
|
export const route = APIUtils.createRouter("/api");
|
||||||
|
|
||||||
@@ -62,4 +63,5 @@ route.router.use(ChecklistRoute.path, ChecklistRoute.router);
|
|||||||
route.router.use(ImagesRoute.path, ImagesRoute.router);
|
route.router.use(ImagesRoute.path, ImagesRoute.router);
|
||||||
route.router.use(CommunityBoardRoute.path, CommunityBoardRoute.router);
|
route.router.use(CommunityBoardRoute.path, CommunityBoardRoute.router);
|
||||||
route.router.use(PlayerEventsRoute.path, PlayerEventsRoute.router);
|
route.router.use(PlayerEventsRoute.path, PlayerEventsRoute.router);
|
||||||
route.router.use(StorefrontsRoute.path, StorefrontsRoute.router);
|
route.router.use(StorefrontsRoute.path, StorefrontsRoute.router);
|
||||||
|
route.router.use(AnnouncementRoute.path, AnnouncementRoute.router);
|
||||||
30
src/routes/api/announcement.ts
Normal file
30
src/routes/api/announcement.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/* 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 { APIUtils } from "../../apiutils.ts";
|
||||||
|
import { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter("/announcement");
|
||||||
|
|
||||||
|
route.router.get('/v1/get',
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
|
APIUtils.emptyArrayResponse
|
||||||
|
|
||||||
|
);
|
||||||
@@ -15,7 +15,8 @@ GNU Affero General Public License for more details.
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
import { APIUtils } from "../../apiutils.ts";
|
import { z } from "zod";
|
||||||
|
import { APIUtils, NoBody } from "../../apiutils.ts";
|
||||||
import UnifiedProfile from "../../data/profiles.ts";
|
import UnifiedProfile from "../../data/profiles.ts";
|
||||||
import { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
@@ -40,14 +41,36 @@ route.router.get('/v1/:id',
|
|||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
route.router.get('/v1/bulk',
|
interface ReputationBulkBody {
|
||||||
|
Ids: string[] | string
|
||||||
|
}
|
||||||
|
const reputationBulkSchema = z.object({
|
||||||
|
Ids: z.union([
|
||||||
|
z.array(z.string()),
|
||||||
|
z.string()
|
||||||
|
])
|
||||||
|
});
|
||||||
|
route.router.post('/v1/bulk',
|
||||||
|
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
APIUtils.AuthenticationType(AuthType.Game),
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
express.urlencoded({ extended: true }),
|
express.urlencoded({ extended: true }),
|
||||||
APIUtils.logBody,
|
APIUtils.validateRequestBody(reputationBulkSchema),
|
||||||
|
|
||||||
APIUtils.emptyArrayResponse
|
async (rq: express.Request<NoBody, {}, ReputationBulkBody>, rs: express.Response) => {
|
||||||
|
if (typeof rq.body.Ids == 'object') {
|
||||||
|
const reputations = rq.body.Ids
|
||||||
|
.map(id => parseInt(id)).filter(id => !isNaN(id)) // parse as int[] and filter out non-numbers
|
||||||
|
.map(id => UnifiedProfile.get(id).Reputation.getReputation()); // get all reputations
|
||||||
|
rs.json(await Promise.all(reputations));
|
||||||
|
} else {
|
||||||
|
const id = parseInt(rq.body.Ids);
|
||||||
|
if (isNaN(id)) {
|
||||||
|
rs.sendStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rs.json([await UnifiedProfile.get(id).Reputation.getReputation()]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -16,10 +16,11 @@ 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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
import Logging from "@proxnet/undead-logging";
|
import Logging from "@proxnet/undead-logging";
|
||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils, NoBody } from "../../apiutils.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import UnifiedProfile from "../../data/profiles.ts";
|
import UnifiedProfile from "../../data/profiles.ts";
|
||||||
import { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
const log = new Logging("ProgressionRoute");
|
const log = new Logging("ProgressionRoute");
|
||||||
|
|
||||||
@@ -51,13 +52,36 @@ route.router.get('/v1/progression/:id',
|
|||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
interface ProgressionBulkBody {
|
||||||
|
Ids: string[] | string
|
||||||
|
}
|
||||||
|
const progressionBulkSchema = z.object({
|
||||||
|
Ids: z.union([
|
||||||
|
z.array(z.string()),
|
||||||
|
z.string()
|
||||||
|
])
|
||||||
|
});
|
||||||
route.router.post('/v1/progression/bulk',
|
route.router.post('/v1/progression/bulk',
|
||||||
|
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
APIUtils.AuthenticationType(AuthType.Game),
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
express.urlencoded({ extended: true }),
|
express.urlencoded({ extended: true }),
|
||||||
APIUtils.logBody,
|
APIUtils.validateRequestBody(progressionBulkSchema),
|
||||||
|
|
||||||
APIUtils.emptyArrayResponse
|
async (rq: express.Request<NoBody, {}, ProgressionBulkBody>, rs: express.Response) => {
|
||||||
|
if (typeof rq.body.Ids == 'object') {
|
||||||
|
const progressions = rq.body.Ids
|
||||||
|
.map(id => parseInt(id)).filter(id => !isNaN(id)) // filter out non-numbers
|
||||||
|
.map(id => UnifiedProfile.get(id).Progression.export()); // get all progressions
|
||||||
|
rs.json(await Promise.all(progressions));
|
||||||
|
} else {
|
||||||
|
const id = parseInt(rq.body.Ids);
|
||||||
|
if (isNaN(id)) {
|
||||||
|
rs.sendStatus(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rs.json([await UnifiedProfile.get(id).Progression.export()]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -16,17 +16,21 @@ 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/>. */
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
|
import { Config } from "../../config.ts";
|
||||||
import { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/quickPlay");
|
export const route = APIUtils.createRouter("/quickPlay");
|
||||||
|
|
||||||
|
const config = Config.getConfig();
|
||||||
|
|
||||||
route.router.get('/v1/getandclear',
|
route.router.get('/v1/getandclear',
|
||||||
|
|
||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
APIUtils.AuthenticationType(AuthType.Game),
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
|
|
||||||
(_rq, rs) => {
|
(_rq, rs) => {
|
||||||
rs.json({});
|
if (!config.public.initialRoom) rs.json({});
|
||||||
|
else rs.json({ RoomName: config.public.initialRoom });
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|||||||
|
|
||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
import Rooms from "../../data/content/rooms.ts";
|
import Rooms from "../../data/content/rooms.ts";
|
||||||
import { RoomAccessibility } from "../../data/content/roomtypes.ts";
|
import { RoomDataTypes } from "../../data/content/rooms/DataTypes.ts";
|
||||||
import { AuthType } from "../../data/users.ts";
|
import { AuthType } from "../../data/users.ts";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ route.router.get('/v1/hot',
|
|||||||
async (_rq, rs) => {
|
async (_rq, rs) => {
|
||||||
// temporary: return all public AG rooms for testing
|
// temporary: return all public AG rooms for testing
|
||||||
const rooms = await Rooms.getAllBuiltinRoomGenerations();
|
const rooms = await Rooms.getAllBuiltinRoomGenerations();
|
||||||
rs.json(rooms.map(room => room.Room).filter(room => room.Accessibility == RoomAccessibility.Public));
|
rs.json(rooms.map(room => room.Room).filter(room => room.Accessibility == RoomDataTypes.RoomAccessibility.Public));
|
||||||
},
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ route.router.put('/statusvisibility',
|
|||||||
APIUtils.Authentication,
|
APIUtils.Authentication,
|
||||||
APIUtils.AuthenticationType(AuthType.Game),
|
APIUtils.AuthenticationType(AuthType.Game),
|
||||||
express.urlencoded({ extended: true }),
|
express.urlencoded({ extended: true }),
|
||||||
APIUtils.logBody,
|
|
||||||
APIUtils.validateRequestBody(StatusVisibilitySchema),
|
APIUtils.validateRequestBody(StatusVisibilitySchema),
|
||||||
|
|
||||||
async (rq: express.Request<NoBody, NoBody, StatusVisibilityBody>, rs: express.Response) => {
|
async (rq: express.Request<NoBody, NoBody, StatusVisibilityBody>, rs: express.Response) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user