diff --git a/src/data/content/rooms.ts b/src/data/content/rooms.ts
index 174d678..ed097d9 100644
--- a/src/data/content/rooms.ts
+++ b/src/data/content/rooms.ts
@@ -15,11 +15,14 @@ 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 . */
-import { it } from "node:test";
import { Redis } from "../../db.ts";
import { RootPath } from "./baseimages.ts";
-import { BuiltinRoom, RoomState } from "./roomtypes.ts";
+import { BuiltinRoom, RoomDetails, RoomState } from "./roomtypes.ts";
import { RoomFetch } from "./room.ts";
+import { Profile } from "../profiles.ts";
+import Logging from "@proxnet/undead-logging";
+
+const log = new Logging("Rooms");
const rooms = JSON.parse(Deno.readTextFileSync(`${RootPath}/res/rooms.json`)) as BuiltinRoom[];
@@ -119,58 +122,166 @@ class RoomsBase {
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, `DefaultProfileImage.png`),
+ 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}`),
+ ]);
+
+ 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.RoomSceneLocationId),
+ 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);
+ }
+ }
+
+
+
+ async cloneRoom(roomid: number, newname: string, newowner: Profile, dorm?: boolean) {
+ const canBeClonedRaw = await Redis.Database.hget(Redis.buildKey(
+ Redis.KeyGroups.Rooms.Root,
+ roomid.toString(),
+ Rooms.roomRootKeys.Meta
+ ), Rooms.roomMetaKeys.CloningAllowed);
+ if (!canBeClonedRaw) return null;
+ let canBeCloned = null;
+ try {
+ canBeCloned = JSON.parse(canBeClonedRaw) as boolean;
+ } catch {
+ return null;
+ }
+ if (!canBeCloned) return null;
+ const beforeRoom = await Rooms.get(roomid); // room must exist
+ if (!beforeRoom || !beforeRoom.Room.CloningAllowed) return null; // room must be cloneable
+ if (await Rooms.getByName(newname)) return null; // room name cannot be taken
+
+ const newId = await this.#getAvailableRoomId();
+ beforeRoom.Room.CreatorPlayerId = newowner.getId();
+ beforeRoom.Room.RoomId = newId;
+ for (const subroom of beforeRoom.Scenes) subroom.RoomId = newId;
+ if (dorm) {
+ beforeRoom.Room.IsAGRoom = true;
+ beforeRoom.Room.IsDormRoom = true;
+ }
+
+ await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, newname), newId);
+ await Rooms.#setRoom(beforeRoom);
+ return beforeRoom;
+
+ }
+
+ async getProfileDormDefault(player: Profile) {
+ const unparsedId = await Redis.Database.hget(Redis.buildKey(
+ Redis.KeyGroups.Rooms.Root,
+ Redis.KeyGroups.Rooms.PlayerDorms
+ ), player.getId().toString());
+ if (unparsedId) {
+ const parsedId = parseInt(unparsedId);
+ if (!isNaN(parsedId)) return await Rooms.get(parsedId);
+ }
+
+ const baseDorm = await Rooms.getByName("DormRoom");
+
+ log.d('got base dorm');
+ if (!baseDorm) return null;
+ log.d('base dorm is not null');
+ const newDorm = await this.cloneRoom(baseDorm.Room.RoomId, "DormRoom", player, true);
+ await Redis.Database.hset(Redis.buildKey(
+ Redis.KeyGroups.Rooms.Root,
+ Redis.KeyGroups.Rooms.PlayerDorms
+ ), player.getId().toString(), baseDorm.Room.RoomId);
+ log.d('got new dorm');
+ if (!newDorm) return null;
+ log.d('new dorm is not null');
+ return newDorm;
+ }
+
async generateBuiltinRooms() {
if ((await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.BuiltinGenerated))) !== null) return true;
for (const builtinRoom of rooms) {
const newId = await this.#getAvailableRoomId();
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, this.miscKeys.AGRooms), newId);
- await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Room_Names, builtinRoom.Name), newId);
- const rootKey = Redis.buildKey(Redis.KeyGroups.Rooms.Root, newId.toString());
-
- const roomMetaRootKey = Redis.buildKey(rootKey, this.roomRootKeys.Meta);
- await Promise.all([
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomId, newId),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Name, builtinRoom.Name),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Description, builtinRoom.Description),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CreatorPlayerId, `1`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.ImageName, `${builtinRoom.Name}.png`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.State, `${RoomState.Active}`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.Accessibility, `${builtinRoom.Accessibility}`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsLevelVoting, `${builtinRoom.SupportsLevelVoting}`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.IsAGRoom, "true"),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.IsDormRoom, "false"),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CloningAllowed, `${builtinRoom.CloningAllowed}`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsScreens, `${builtinRoom.SupportsScreens}`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsWalkVR, `${builtinRoom.SupportsWalkVR}`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.SupportsTeleportVR, `${builtinRoom.SupportsTeleportVR}`),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.AllowsJuniors, "true"),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.RoomWarningMask, 0),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.CustomRoomWarning, ""),
- Redis.Database.hset(roomMetaRootKey, this.roomMetaKeys.DisableMicAutoMute, "false"),
- ]);
-
- for (const subroom of builtinRoom.Scenes) {
- const newSubId = await this.#getAvailableSubRoomId(newId);
- const subRootMetaKey = Redis.buildKey(
- Redis.KeyGroups.Rooms.Root,
- newId.toString(),
- this.roomRootKeys.Subrooms,
- newSubId.toString(),
- this.subroomRootKeys.Meta
- );
-
- await Promise.all([
- Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.RoomSceneLocationId, subroom.RoomSceneLocationId),
- Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.Name, subroom.RoomSceneLocationId),
- Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.IsSandbox, `${subroom.IsSandbox}`),
- Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.DataBlobName, ""),
- Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.MaxPlayers, subroom.MaxPlayers),
- Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.CanMatchmakeInto, `${subroom.CanMatchmakeInto}`),
- Redis.Database.hset(subRootMetaKey, this.subroomMetaKeys.DataModifiedAt, new Date().toISOString()),
- ]);
- await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Rooms.Root, newId.toString(), this.roomRootKeys.Subrooms), newSubId);
+ 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.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;
diff --git a/src/data/content/roomtypes.ts b/src/data/content/roomtypes.ts
index a2c75c2..854670a 100644
--- a/src/data/content/roomtypes.ts
+++ b/src/data/content/roomtypes.ts
@@ -101,6 +101,18 @@ export interface BuiltinRoom {
Scenes: BuiltinScene[]
}
+export interface RoomScene {
+ RoomSceneId: number,
+ RoomId: number,
+ RoomSceneLocationId: string,
+ Name: string,
+ IsSandbox: boolean,
+ DataBlobName: string,
+ MaxPlayers: number,
+ CanMatchmakeInto?: boolean,
+ DataModifiedAt: string
+}
+
export interface Room {
RoomId: number,
Name: string,
@@ -144,18 +156,6 @@ export interface TagDTO {
Type: TagType
}
-export interface RoomScene {
- RoomSceneId: number,
- RoomId: number,
- RoomSceneLocationId: string,
- Name: string,
- IsSandbox: boolean,
- DataBlobName: string,
- MaxPlayers: number,
- CanMatchmakeInto?: boolean,
- DataModifiedAt: string
-}
-
export interface RoomDetails {
Room: Room,
Scenes: RoomScene[],
diff --git a/src/data/live/base.ts b/src/data/live/base.ts
index e197fbf..1cc4efd 100644
--- a/src/data/live/base.ts
+++ b/src/data/live/base.ts
@@ -15,6 +15,8 @@ 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 . */
+import Rooms from "../content/rooms.ts";
+import { RoomAccessibility, RoomState } from "../content/roomtypes.ts";
import { Profile } from "../profiles.ts";
import Instances from "./instances.ts";
import { MatchmakingErrorCode, RoomInstance } from "./types.ts";
@@ -25,7 +27,7 @@ interface MatchmakingOptions {
roomName: string,
subRoomName?: string,
private?: boolean,
- instanceId?: string,
+ instanceId?: number,
profile: Profile
}
@@ -51,30 +53,55 @@ class MatchmakingBase {
loginLocks.delete(prof.getId());
}
- matchmake(options: MatchmakingOptions) {
+ async matchmake(options: MatchmakingOptions) {
if (options.instanceId) {
- // get instance
- // if instance exists, check private
-
- }
- /*
- if roomname is dormroom, create the profile's dormroom if it does not exist
- set the target room to the profile's dormroom id
+ const instance = Instances.getInstance(options.instanceId);
+ if (instance) {
- get all public instances for roomname
- filter out private and full instances
- if a subroomname was specified, filter out subrooms that are not that subroom
+ if (Instances.playerIsInInstance(options.profile, instance)) return { errorCode: MatchmakingErrorCode.AlreadyInTargetInstance };
+ else {
+ if (instance.isFull) return { errorCode: MatchmakingErrorCode.InsufficientSpace }
+ else if (instance.isPrivate) return { errorCode: MatchmakingErrorCode.RoomInstanceIsPrivate };
+
+ Instances.setPlayerInstance(options.profile, instance);
+ return { errorCode: MatchmakingErrorCode.Success, roomInstance: instance };
+ }
+
+ } else return { errorCode: MatchmakingErrorCode.NoSuchGame }
+
+ } else {
+
+ const targetRoom = options.roomName !== 'DormRoom' ? await Rooms.getByName(options.roomName) : await Rooms.getProfileDormDefault(options.profile);
+ if (!targetRoom) return { errorCode: MatchmakingErrorCode.NoSuchRoom };
+ if (targetRoom.Room.Accessibility == RoomAccessibility.Private) return { errorCode: MatchmakingErrorCode.RoomIsPrivate };
+ if (targetRoom.Room.State !== RoomState.Active) return { errorCode: MatchmakingErrorCode.RoomIsNotActive };
+ const roomId = targetRoom.Room.RoomId;
+
+ let allInstances = Instances.getAllRoomInstances(roomId).values().toArray().filter(instance => !instance.isPrivate && !instance.isFull);
+ const subroomId = targetRoom.Scenes.find(scene => scene.Name == options.subRoomName)?.RoomSceneId;
+ if (subroomId) allInstances = allInstances.filter(instance => instance.subRoomId == subroomId);
+
+ const foundInstance = allInstances[Math.floor(Math.random() * allInstances.length)];
+ if (!foundInstance) {
+
+ const matchmakeableSubrooms = targetRoom.Scenes.filter(scene => scene.CanMatchmakeInto);
+ const newInstance = Instances.createInstance({
+ Room: targetRoom,
+ SceneIndex: Math.floor(Math.random() * matchmakeableSubrooms.length),
+ FirstPlayer: options.profile
+ });
+ return { errorCode: MatchmakingErrorCode.Success, roomInstance: newInstance };
+
+ } else {
+
+ Instances.setPlayerInstance(options.profile, foundInstance);
+ return { errorCode: MatchmakingErrorCode.Success, roomInstance: foundInstance };
+
+ }
- pick an instance of the given instances at random
- if none exist, create one
- use only matchmakeable subrooms
- if none are matchmakeable, go to "Home"
- if "Home" does not exist, go to the first index
-
- go to that instance
- */
+ }
}
diff --git a/src/data/live/instances.ts b/src/data/live/instances.ts
index 8eeb54d..96b3bab 100644
--- a/src/data/live/instances.ts
+++ b/src/data/live/instances.ts
@@ -29,10 +29,6 @@ const instancePlayers: Map> = new Map();
* `Map`
*/
const instanceMap: Map> = new Map();
-/**
- * `Map`
- */
-const instanceRoomMap: Map = new Map();
class InstancesBase {
diff --git a/src/db.ts b/src/db.ts
index 5bf661e..be5d084 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -106,6 +106,7 @@ export const KeyGroups = {
Room_Names: "room-names",
Rooms: {
Root: "room",
+ PlayerDorms: "player-dormids"
},
Operators: "operators",
Users: {
diff --git a/src/routes/match/goto.ts b/src/routes/match/goto.ts
index 534e9a7..030288c 100644
--- a/src/routes/match/goto.ts
+++ b/src/routes/match/goto.ts
@@ -33,14 +33,30 @@ route.router.post('/room/:roomName',
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Game),
- (rq: express.Request, rs: express.Response) => {
+ async (rq: express.Request, rs: express.Response) => {
if (!rq.params.roomName) {
rs.json({
errorCode: MatchmakingErrorCode.NoSuchRoom
});
return;
}
- rs.json(Matchmaking.matchmake({ profile: rs.locals.profile, roomName: rq.params.roomName }));
+ rs.json(await Matchmaking.matchmake({ profile: rs.locals.profile, roomName: rq.params.roomName }));
},
-);
\ No newline at end of file
+);
+route.router.post('/room/:roomName/:subRoomName',
+
+ APIUtils.Authentication,
+ APIUtils.AuthenticationType(AuthType.Game),
+
+ async (rq: express.Request, rs: express.Response) => {
+ if (!rq.params.roomName) {
+ rs.json({
+ errorCode: MatchmakingErrorCode.NoSuchRoom
+ });
+ return;
+ }
+ rs.json(await Matchmaking.matchmake({ profile: rs.locals.profile, roomName: rq.params.roomName, subRoomName: rq.params.subRoomName }));
+ },
+
+)
\ No newline at end of file