This commit is contained in:
2025-09-03 14:32:02 -04:00
parent c6e2b6e4d3
commit 03b751dda6
22 changed files with 1429 additions and 150 deletions

View File

@@ -1,53 +1,273 @@
import Logging from "@proxnet/undead-logging";
import type KV from "../../persistence/kv.ts";
import { type ServerBase } from "../../server.ts";
import { DatabaseRoom, RoomDataTypes } from "./DataTypes.ts";
import { DatabaseRoom, FactoryMode, GalvanicTagDTO, RoomDataTypes, TagDTO, TagType, WriteMode } from "./RoomDataTypes.ts";
import { SubroomFactory } from "./SubroomFactory.ts";
import { ServerRoomsBase } from "../base.ts";
export interface RoomFactoryOptions {
const log = new Logging("RoomFactory");
const roomDebug = true;
interface FactoryOptionsBase {
mode: FactoryMode,
id?: number,
name?: string,
name?: string
}
export interface WriteFactoryOptions extends FactoryOptionsBase {
mode: FactoryMode.Write,
writeMode: WriteMode,
}
export interface FetchFactoryOptions extends FactoryOptionsBase {
mode: FactoryMode.Fetch
}
export type FactoryOptions = WriteFactoryOptions | FetchFactoryOptions;
export class RoomFactory {
static roomsKey = "rooms";
#server: ServerBase;
#kv: KV;
#roomId: number | undefined;
#name: string | undefined;
#description: string | undefined;
#creatorPlayerId: number | undefined;
#imageName: string | undefined;
#state: RoomDataTypes.RoomScene | undefined;
#accessibility: RoomDataTypes.RoomAccessibility | undefined;
#supportsLevelVoting: boolean | undefined;
#isAGRoom: boolean | undefined;
#isDormRoom: boolean | undefined;
#cloningAllowed: boolean | undefined;
#supportsScreens: boolean | undefined;
#supportsWalkVR: boolean | undefined;
#supportsTeleportVR: boolean | undefined;
#allowsJuniors: boolean | undefined;
#roomWarningMask: number | undefined;
#customRoomWarning: string | undefined;
#disableMicAutoMute: boolean | undefined;
#dbMeta: DatabaseRoom | null = null;
factoryMode: FactoryMode = FactoryMode.Fetch;
writeMode: WriteMode = WriteMode.WriteIfFree;
#visits: number | undefined;
#hardware
#obj: DatabaseRoom | null = null;
#hardwareSupport: Set<RoomDataTypes.HardwareSupport> | null = null;
#tags: Set<string> | null = null;
#cannotAccessBeforeInitError = new Error("Cannot access properties before initialization");
#cannotWriteBeforeInitError = new Error("Cannot write before initialization");
constructor(server: ServerBase, kv: KV) {
this.#server = server;
this.#kv = kv;
}
async init(options: RoomFactoryOptions) {
async init(options: FactoryOptions) {
if (typeof options.id == 'undefined' && typeof options.name == 'undefined')
throw new Error("Must specify a room ID or a room name");
if (typeof options.id !== 'undefined') this.#roomId = options.id;
else {
if (!options.name) throw new Error("Room name must be provided");
const id = await this.#server.Rooms.getIdFromName(options.name);
if (id == null) throw new Error("Room name not found");
this.#roomId = id;
}
const obj = await this.#kv.getKv().get<DatabaseRoom>([RoomFactory.roomsKey, this.#roomId, 'meta']);
if (obj.value == null) return null;
else this.#obj = obj.value;
const hardwareSupport = await this.#kv.getKv().get<Set<RoomDataTypes.HardwareSupport>>([RoomFactory.roomsKey, this.#roomId, 'hardware']);
if (hardwareSupport.value == null) return null;
else this.#hardwareSupport = hardwareSupport.value;
const tags = await this.#kv.getKv().get<Set<string>>([RoomFactory.roomsKey, this.#roomId, 'tags']);
this.#tags = tags.value;
this.factoryMode = options.mode;
if (options.mode == FactoryMode.Write) this.writeMode = options.writeMode;
return this;
}
async write() {
if (typeof this.#roomId !== 'number') throw this.#cannotWriteBeforeInitError;
const w = async () => {
if (typeof this.#roomId !== 'number') throw this.#cannotWriteBeforeInitError;
await Promise.all([
this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'meta'], this.#obj),
this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'hardware'], this.#hardwareSupport),
this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'tags'], this.#tags),
]);
if (!this.IsDormRoom) this.#kv.getKv().set([ServerRoomsBase.roomNamesKey, this.Name], this.#roomId);
else this.#kv.getKv().set([ServerRoomsBase.playerDormsKey, this.CreatorPlayerId], this.#roomId);
this.#server.emit('room.updated', { room: this });
if (roomDebug) log.d(`Room ${this.#roomId} written and emitted`);
}
if (this.factoryMode == FactoryMode.Fetch) throw new Error("Cannot write in fetch mode");
const key = [RoomFactory.roomsKey, this.#roomId, 'meta'];
const val = await this.#kv.getKv().get(key);
if (val == null) await w();
else {
if (this.writeMode == WriteMode.Overwrite) await w();
else throw new Error("Room already exists");
}
}
async export(): Promise<RoomDataTypes.RoomDetails> {
if (!this.#obj || !this.#roomId) throw this.#cannotAccessBeforeInitError;
const obj = this.#obj;
const hardwareSupport = this.getHardwareSupport();
const autoTags = this.getTags();
const galvTags = this.getGalvanicTags();
const subroomExports = (await Promise.all(
this.getSubrooms().map(subroom => this.getSubroom(subroom))
)).map(factory => factory.export());
return {
Room: {
RoomId: this.#roomId,
Name: obj.Room.Name,
Description: obj.Room.Description,
CreatorPlayerId: obj.Room.CreatorPlayerId,
ImageName: obj.Room.ImageName,
State: obj.Room.State,
Accessibility: obj.Room.Accessibility,
SupportsLevelVoting: obj.Room.SupportsLevelVoting,
IsAGRoom: obj.Room.IsAGRoom,
IsDormRoom: obj.Room.IsDormRoom,
CloningAllowed: obj.Room.CloningAllowed,
AllowsJuniors: obj.Room.AllowsJuniors,
RoomWarningMask: obj.Room.RoomWarningMask,
CustomRoomWarning: obj.Room.CustomRoomWarning,
DisableMicAutoMute: obj.Room.DisableMicAutoMute,
SupportsScreens: hardwareSupport.has('screens'),
SupportsWalkVR: hardwareSupport.has('walk_vr'),
SupportsTeleportVR: hardwareSupport.has('teleport_vr'),
SupportsMobile: hardwareSupport.has('mobile'),
SupportsVRLow: hardwareSupport.has('low_vr')
},
Scenes: subroomExports,
Tags: autoTags.concat(galvTags),
CoOwners: [],
InvitedCoOwners: [],
Hosts: [],
InvitedHosts: [],
CheerCount: 0,
FavoriteCount: 0,
VisitCount: await this.getVisitCount()
}
}
getSubrooms() {
if (!this.#obj) throw this.#cannotAccessBeforeInitError;
return this.#obj.Subrooms;
}
async getSubroom(id: number) {
return await new SubroomFactory(this.#server, this.#kv).init({ mode: FactoryMode.Fetch, writeMode: WriteMode.WriteIfFree, id });
}
get Name() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Name }
set Name(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Name = data }
get Description() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Description }
set Description(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Description = data }
get CreatorPlayerId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.CreatorPlayerId }
set CreatorPlayerId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.CreatorPlayerId = data }
get ImageName() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.ImageName }
set ImageName(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.ImageName = data }
get State() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.State }
set State(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.State = data }
get RoomAccessibility() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Accessibility }
set RoomAccessibility(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Accessibility = data }
get SupportsLevelVoting() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.SupportsLevelVoting }
set SupportsLevelVoting(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.SupportsLevelVoting = data }
get IsAGRoom() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.IsAGRoom }
set IsAGRoom(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.IsAGRoom = data }
get IsDormRoom() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.IsDormRoom }
set IsDormRoom(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.IsDormRoom = data }
get CloningAllowed() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.CloningAllowed }
set CloningAllowed(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.CloningAllowed = data }
get AllowsJuniors() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.AllowsJuniors }
set AllowsJuniors(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.AllowsJuniors = data }
get RoomWarningMask() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.RoomWarningMask }
set RoomWarningMask(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.RoomWarningMask = data }
get CustomRoomWarning() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.CustomRoomWarning }
set CustomRoomWarning(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.CustomRoomWarning = data }
get DisableMicAutoMute() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.DisableMicAutoMute }
set DisableMicAutoMute(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.DisableMicAutoMute = data }
getHardwareSupport() {
if (!this.#hardwareSupport) throw this.#cannotAccessBeforeInitError;
return this.#hardwareSupport;
}
async #saveHardwareSupport() {
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
await this.#kv.getKv().set([this.#roomId, 'hardware'], this.#hardwareSupport);
}
async addHardwareSupport(...hardware: RoomDataTypes.HardwareSupport[]) {
if (!this.#hardwareSupport || !this.#roomId) throw this.#cannotAccessBeforeInitError;
if (typeof hardware == 'string') {
this.#hardwareSupport.add(hardware);
await this.#saveHardwareSupport();
} else {
for (const hw of hardware) this.#hardwareSupport.add(hw);
await this.#saveHardwareSupport();
}
}
async removeHardwareSupport(hardware: RoomDataTypes.HardwareSupport) {
if (!this.#hardwareSupport) throw this.#cannotAccessBeforeInitError;
this.#hardwareSupport.delete(hardware);
await this.#saveHardwareSupport();
}
/**
* Visits are fetched during every access, not during init
*/
async getVisitCount() {
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
const val = await this.#kv.getKv().get<number>([RoomFactory.roomsKey, this.#roomId, 'visits']);
if (val.value == null) return 0;
else return val.value;
}
/**
* Visits are fetched during every access, not during init
*/
async addVisit() {
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
const visits = await this.getVisitCount();
await this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'visits'], visits + 1);
}
getGalvanicTags(): GalvanicTagDTO[] {
const tags: GalvanicTagDTO[] = [];
if (this.IsAGRoom) tags.push({ Tag: "recroomoriginal", Type: TagType.AGOnly });
if (this.IsDormRoom) tags.push({ Tag: "dormroom", Type: TagType.Auto });
if (this.Name === "Paintball" || this.Name === "PaintballVR") tags.push({ Tag: "paintball", Type: TagType.Auto });
const hardwareSupport = this.getHardwareSupport();
if (hardwareSupport.has('screens')) tags.push({ Tag: "screen", Type: TagType.Auto });
if (hardwareSupport.has('walk_vr')) tags.push({ Tag: "walkvr", Type: TagType.Auto });
if (hardwareSupport.has('teleport_vr')) tags.push({ Tag: "teleportvr", Type: TagType.Auto });
if (this.AllowsJuniors) tags.push({ Tag: "junior", Type: TagType.Auto });
return tags;
}
getTags(): TagDTO[] {
if (!this.#tags) return [];
return this.#tags.values().toArray().map(val => ({ Tag: val, Type: TagType.General }));
}
setTags(tags: Set<string>) {
this.#tags = tags;
}
}