forked from zombieb/galvanic-corrosion-rewrite
roooooms 2
This commit is contained in:
@@ -45,8 +45,8 @@ export class Instance {
|
||||
getPlayers() {
|
||||
return this.#players.values().toArray();
|
||||
}
|
||||
playerIsHere(profile: Profile) {
|
||||
return Boolean(this.getPlayers().find(prof => prof.same(profile)));
|
||||
hasPlayer(profile: Profile) {
|
||||
return this.#players.values().some(prof => prof.getId() === profile.getId());
|
||||
}
|
||||
removePlayer(profile: Profile) {
|
||||
this.#players.delete(profile);
|
||||
|
||||
@@ -3,19 +3,146 @@ import { ServerContentBase } from "../ContentBase.ts";
|
||||
import KV from "../persistence/kv.ts";
|
||||
import type Profile from "../profiles/profile.ts";
|
||||
import { RoomFactory } from "./internal/RoomFactory.ts";
|
||||
import { FactoryMode } from "./internal/RoomDataTypes.ts";
|
||||
import { FactoryMode, HardwareSupports, RoomDataTypes, WriteMode } from "./internal/RoomDataTypes.ts";
|
||||
import { AGRoom, AGRoomLocation, AGRoomRuntimeConfig } from "./internal/ClientRoomTypes.ts";
|
||||
import Command from "../commands/command.ts";
|
||||
import z from "zod";
|
||||
import { RoomLocation } from "../instances/base.ts";
|
||||
|
||||
export class ServerRoomsBase extends ServerContentBase {
|
||||
|
||||
#subroomKv = new KV('subrooms', true);
|
||||
#log = new Logging("Rooms");
|
||||
|
||||
static agRoomIdsKey = "agrooms";
|
||||
static baseRoomIdsKey = "baserooms";
|
||||
|
||||
static roomNamesKey = "room_names";
|
||||
static playerDormsKey = "dorms";
|
||||
|
||||
protected override async start() {
|
||||
#agrooms: Set<number> = new Set();
|
||||
#baserooms: Set<number> = new Set();
|
||||
|
||||
override async start() {
|
||||
await this.#subroomKv.init();
|
||||
this.#log.i('[sub]rooms database initialized');
|
||||
|
||||
const agrooms = await this.kv.getKv().get<Set<number>>([ServerRoomsBase.agRoomIdsKey]);
|
||||
if (agrooms.value !== null) this.#agrooms = agrooms.value;
|
||||
this.#log.i(`${this.#agrooms.size} AG rooms exist`);
|
||||
|
||||
const baserooms = await this.kv.getKv().get<Set<number>>([ServerRoomsBase.baseRoomIdsKey]);
|
||||
if (baserooms.value !== null) this.#baserooms = baserooms.value;
|
||||
|
||||
this.server.Commands.addRootCommand(new Command({
|
||||
key: ["rooms", "r", "room"],
|
||||
subcommands: [
|
||||
new Command({
|
||||
key: ["initag", "initagrooms", "initagroom", "iag"],
|
||||
zod: z.tuple([]).rest(z.string()),
|
||||
exec: async (...arrayPath: string[]) => {
|
||||
const path = arrayPath.join(' ');
|
||||
try {
|
||||
const config = JSON.parse((await Deno.readTextFile(path)).toString()) as AGRoomRuntimeConfig;
|
||||
this.#log.d('Starting AG room initialization');
|
||||
this.initBuiltinRooms(config.Rooms, config.Locations);
|
||||
} catch (err) {
|
||||
return err as Error;
|
||||
}
|
||||
},
|
||||
help: "Initialize AG rooms with AGRoomRuntimeConfig from provided file path"
|
||||
}),
|
||||
new Command({
|
||||
key: ["getplayerdorm", "playerdorm", "pd", "dorm"],
|
||||
zod: z.tuple([z.coerce.number().min(1).max(Math.pow(2, 31))]),
|
||||
exec: async (playerId: number) => {
|
||||
const factory = await this.getPlayerDorm(this.server.Profiles.get(playerId))
|
||||
},
|
||||
help: "Get the domroom information for a certain profile/player"
|
||||
})
|
||||
],
|
||||
}))
|
||||
}
|
||||
async #writeAgRooms() {
|
||||
await this.kv.getKv().set([ServerRoomsBase.agRoomIdsKey], this.#agrooms);
|
||||
}
|
||||
async #writeBaseRooms() {
|
||||
await this.kv.getKv().set([ServerRoomsBase.baseRoomIdsKey], this.#baserooms);
|
||||
}
|
||||
|
||||
async initBuiltinRooms(rooms: AGRoom[], locations: AGRoomLocation[]) {
|
||||
await Promise.all(rooms.map(async room => {
|
||||
if (room.Accessibility == RoomDataTypes.RoomAccessibility.Private) return;
|
||||
if ([
|
||||
"ArtTesting",
|
||||
"AnimationRecordingStudio",
|
||||
"Calibration",
|
||||
"ARRoom",
|
||||
"Registration",
|
||||
"DormRoom"
|
||||
].includes(room.Name)) return;
|
||||
|
||||
const roomFactory = await this.write();
|
||||
if (roomFactory == null) {
|
||||
this.#log.w(`No factory given while writing builtin room "${room.Name}"!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const supportsVRLow = room.Scenes.map(scene => locations.find(loc => loc.ReplicationId == scene.RoomSceneLocationId))
|
||||
.filter(val => typeof val !== 'undefined').some(loc => loc.SupportsVRLow) ?? false;
|
||||
const supportsMobile = room.Scenes.map(scene => locations.find(loc => loc.ReplicationId == scene.RoomSceneLocationId))
|
||||
.filter(val => typeof val !== 'undefined').some(loc => loc.SupportsMobile) ?? false;
|
||||
|
||||
roomFactory.Name = room.Name;
|
||||
roomFactory.Description = room.Description;
|
||||
roomFactory.Accessibility = room.Accessibility;
|
||||
roomFactory.State = RoomDataTypes.RoomState.Active;
|
||||
roomFactory.Description = room.Description;
|
||||
roomFactory.IsAGRoom = true;
|
||||
roomFactory.CloningAllowed = room.CloningAllowed;
|
||||
roomFactory.ImageName = `${room.Name}.png`
|
||||
|
||||
const supportPromises: Promise<unknown>[] = [];
|
||||
roomFactory.removeAllHardwareSupport();
|
||||
if (room.SupportsScreens) supportPromises.push(roomFactory.addHardwareSupport("screens"));
|
||||
if (room.SupportsWalkVR) supportPromises.push(roomFactory.addHardwareSupport("walk_vr"));
|
||||
if (room.SupportsTeleportVR) supportPromises.push(roomFactory.addHardwareSupport("teleport_vr"));
|
||||
if (supportsMobile) supportPromises.push(roomFactory.addHardwareSupport("mobile"));
|
||||
if (supportsVRLow) supportPromises.push(roomFactory.addHardwareSupport("low_vr"));
|
||||
|
||||
await Promise.all(room.Scenes.map(async scene => {
|
||||
const subroomFactory = await roomFactory.newSubroom();
|
||||
subroomFactory.addSave("");
|
||||
|
||||
subroomFactory.RoomId = roomFactory.getRoomId();
|
||||
subroomFactory.Name = scene.Name;
|
||||
subroomFactory.RoomSceneLocationId = scene.RoomSceneLocationId;
|
||||
subroomFactory.IsSandbox = scene.IsSandbox;
|
||||
subroomFactory.CanMatchmakeInto = scene.CanMatchmakeInto;
|
||||
subroomFactory.MaxPlayers = scene.MaxPlayers;
|
||||
|
||||
await subroomFactory.write();
|
||||
|
||||
roomFactory.addSubroom(subroomFactory.RoomSceneId);
|
||||
}));
|
||||
|
||||
await Promise.all(supportPromises);
|
||||
|
||||
this.#agrooms.add(roomFactory.getRoomId());
|
||||
await roomFactory.write();
|
||||
|
||||
if (room.CloningAllowed) this.#baserooms.add(roomFactory.getRoomId());
|
||||
}));
|
||||
|
||||
await this.#writeAgRooms();
|
||||
await this.#writeBaseRooms();
|
||||
this.#log.i(`${this.#agrooms.size} AG rooms added: [${this.#agrooms.values().toArray().join(',')}]`);
|
||||
}
|
||||
|
||||
async getAvailableRoomId() {
|
||||
let id = Math.round(Math.random() * Math.pow(2, 31));
|
||||
while ((await this.kv.getKv().get<unknown>([RoomFactory.roomsKey, id, 'meta'])).value !== null) id = await this.getAvailableRoomId();
|
||||
return id;
|
||||
}
|
||||
|
||||
getKv() {
|
||||
@@ -30,11 +157,48 @@ export class ServerRoomsBase extends ServerContentBase {
|
||||
|
||||
async getPlayerDorm(profile: Profile) {
|
||||
const id = await this.kv.getKv().get<number>([ServerRoomsBase.playerDormsKey, profile.getId()]);
|
||||
if (id.value == null) return null;
|
||||
if (id.value == null) {
|
||||
const roomFactory = await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Write, writeMode: WriteMode.WriteIfFree, id: await this.getAvailableRoomId() });
|
||||
if (!roomFactory) return null;
|
||||
roomFactory.setRoomProperties({
|
||||
Name: `DormRoom`,
|
||||
Description: "Your private dorm.",
|
||||
CreatorPlayerId: profile.getId(),
|
||||
ImageName: "",
|
||||
State: RoomDataTypes.RoomState.Active,
|
||||
Accessibility: RoomDataTypes.RoomAccessibility.Private,
|
||||
SupportsLevelVoting: false,
|
||||
IsAGRoom: false,
|
||||
IsDormRoom: true,
|
||||
CloningAllowed: false,
|
||||
AllowsJuniors: true,
|
||||
RoomWarningMask: 0,
|
||||
CustomRoomWarning: "",
|
||||
DisableMicAutoMute: null
|
||||
});
|
||||
|
||||
await roomFactory.addHardwareSupport(...HardwareSupports);
|
||||
roomFactory.setTags(new Set(["dormroom"]));
|
||||
|
||||
const subroomFactory = await roomFactory.newSubroom();
|
||||
subroomFactory.CanMatchmakeInto = true;
|
||||
subroomFactory.IsSandbox = true;
|
||||
subroomFactory.MaxPlayers = 4;
|
||||
subroomFactory.Name = "Home";
|
||||
subroomFactory.RoomId = roomFactory.getRoomId();
|
||||
subroomFactory.RoomSceneLocationId = RoomLocation.DormRoom;
|
||||
subroomFactory.addSave("");
|
||||
roomFactory.addSubroom(subroomFactory.RoomSceneId);
|
||||
|
||||
await subroomFactory.write();
|
||||
await roomFactory.write();
|
||||
|
||||
return roomFactory;
|
||||
}
|
||||
else return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id.value });
|
||||
}
|
||||
|
||||
async getFromName(name: string) {
|
||||
async getByName(name: string) {
|
||||
const id = await this.getIdFromName(name);
|
||||
if (id == null) return null;
|
||||
return await this.get(id);
|
||||
@@ -44,4 +208,8 @@ export class ServerRoomsBase extends ServerContentBase {
|
||||
return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id });
|
||||
}
|
||||
|
||||
async write(mode?: WriteMode) {
|
||||
return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Write, writeMode: mode ? mode : WriteMode.WriteIfFree, id: await this.getAvailableRoomId() });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,4 +55,9 @@ export interface AGRoomLocation {
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface AGRoomRuntimeConfig {
|
||||
Locations: AGRoomLocation[],
|
||||
Rooms: AGRoom[]
|
||||
}
|
||||
@@ -8,6 +8,9 @@ export enum FactoryMode {
|
||||
Write = 'write'
|
||||
}
|
||||
|
||||
/**
|
||||
* All clients RecRoom can run on.
|
||||
*/
|
||||
export const HardwareSupports = ["screens", "walk_vr", "teleport_vr", "low_vr", "mobile"] as const;
|
||||
export type HardwareSupport = typeof HardwareSupports[number];
|
||||
|
||||
@@ -138,8 +141,7 @@ export interface DatabaseRoomContent {
|
||||
}
|
||||
export interface DatabaseRoom {
|
||||
Hardware: Set<HardwareSupport>,
|
||||
Subrooms: number[],
|
||||
Tags: Set<number>,
|
||||
Tags: Set<string>,
|
||||
CoOwners: Set<number>,
|
||||
InvitedCoOwners: Set<number>,
|
||||
Hosts: Set<number>,
|
||||
@@ -154,14 +156,13 @@ export interface DatabaseSubroom {
|
||||
IsSandbox: boolean,
|
||||
MaxPlayers: number,
|
||||
CanMatchmakeInto: boolean,
|
||||
LatestSaveId: number,
|
||||
Saves: RoomSave[]
|
||||
LatestSaveId: number | null,
|
||||
}
|
||||
|
||||
export type RoomSaveMap = Map<number, RoomSave>
|
||||
export interface RoomSave {
|
||||
SaveId: number,
|
||||
DataBlobName: string,
|
||||
SavedAt: string
|
||||
SavedAt: Date
|
||||
}
|
||||
|
||||
export * as RoomDataTypes from "./RoomDataTypes.ts";
|
||||
@@ -1,17 +1,17 @@
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import type KV from "../../persistence/kv.ts";
|
||||
import { type ServerBase } from "../../server.ts";
|
||||
import { DatabaseRoom, FactoryMode, GalvanicTagDTO, RoomDataTypes, TagDTO, TagType, WriteMode } from "./RoomDataTypes.ts";
|
||||
import { DatabaseRoom, FactoryMode, GalvanicTagDTO, HardwareSupports, RoomAccessibility, RoomDataTypes, RoomState, TagDTO, TagType, WriteMode } from "./RoomDataTypes.ts";
|
||||
import { SubroomFactory } from "./SubroomFactory.ts";
|
||||
import { ServerRoomsBase } from "../base.ts";
|
||||
|
||||
const log = new Logging("RoomFactory");
|
||||
|
||||
const roomDebug = true;
|
||||
const roomDebug = false;
|
||||
|
||||
interface FactoryOptionsBase {
|
||||
mode: FactoryMode,
|
||||
id?: number,
|
||||
id: number,
|
||||
name?: string
|
||||
}
|
||||
export interface WriteFactoryOptions extends FactoryOptionsBase {
|
||||
@@ -36,6 +36,7 @@ export class RoomFactory {
|
||||
writeMode: WriteMode = WriteMode.WriteIfFree;
|
||||
|
||||
#obj: DatabaseRoom | null = null;
|
||||
#subrooms: Set<number> | null = null;
|
||||
#hardwareSupport: Set<RoomDataTypes.HardwareSupport> | null = null;
|
||||
#tags: Set<string> | null = null;
|
||||
|
||||
@@ -49,6 +50,22 @@ export class RoomFactory {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the factory. Retrieves the room from the database and populates factory values.
|
||||
*
|
||||
* Does not fetch subroom content, only available subroom IDs.
|
||||
*
|
||||
* When using write mode, values are initialized to defaults.
|
||||
*
|
||||
* Defaults:
|
||||
* - All hardware is supported
|
||||
* - Cloning is not allowed
|
||||
* - Room is not AG or dorm
|
||||
* - State is Moderation_Closed
|
||||
* - Accessibility is Unlisted
|
||||
* - CreatorPlayerId is 1 (Coach)
|
||||
* - Name and Description are empty strings
|
||||
*/
|
||||
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");
|
||||
@@ -63,11 +80,38 @@ export class RoomFactory {
|
||||
}
|
||||
|
||||
const obj = await this.#kv.getKv().get<DatabaseRoom>([RoomFactory.roomsKey, this.#roomId, 'meta']);
|
||||
if (obj.value == null) return null;
|
||||
else this.#obj = obj.value;
|
||||
if (options.mode == FactoryMode.Fetch && obj.value == null) return null;
|
||||
else this.#obj = options.mode == FactoryMode.Fetch ? obj.value : {
|
||||
Hardware: new Set(HardwareSupports),
|
||||
Tags: new Set(),
|
||||
CoOwners: new Set(),
|
||||
InvitedCoOwners: new Set(),
|
||||
Hosts: new Set(),
|
||||
InvitedHosts: new Set(),
|
||||
Room: {
|
||||
Name: "",
|
||||
Description: "",
|
||||
CreatorPlayerId: 1,
|
||||
ImageName: "",
|
||||
State: RoomState.Moderation_Closed,
|
||||
Accessibility: RoomAccessibility.Unlisted,
|
||||
SupportsLevelVoting: false,
|
||||
IsAGRoom: false,
|
||||
IsDormRoom: false,
|
||||
CloningAllowed: false,
|
||||
AllowsJuniors: true,
|
||||
RoomWarningMask: 0,
|
||||
CustomRoomWarning: "",
|
||||
DisableMicAutoMute: null
|
||||
}
|
||||
};
|
||||
|
||||
const subrooms = await this.#kv.getKv().get<Set<number>>([RoomFactory.roomsKey, this.#roomId, "subrooms"]);
|
||||
if (options.mode == FactoryMode.Write || subrooms.value == null) this.#subrooms = new Set();
|
||||
else this.#subrooms = subrooms.value;
|
||||
|
||||
const hardwareSupport = await this.#kv.getKv().get<Set<RoomDataTypes.HardwareSupport>>([RoomFactory.roomsKey, this.#roomId, 'hardware']);
|
||||
if (hardwareSupport.value == null) return null;
|
||||
if (hardwareSupport.value == null) this.#hardwareSupport = new Set(HardwareSupports);
|
||||
else this.#hardwareSupport = hardwareSupport.value;
|
||||
|
||||
const tags = await this.#kv.getKv().get<Set<string>>([RoomFactory.roomsKey, this.#roomId, 'tags']);
|
||||
@@ -102,7 +146,7 @@ export class RoomFactory {
|
||||
|
||||
const key = [RoomFactory.roomsKey, this.#roomId, 'meta'];
|
||||
const val = await this.#kv.getKv().get(key);
|
||||
if (val == null) await w();
|
||||
if (val.value == null) await w();
|
||||
else {
|
||||
if (this.writeMode == WriteMode.Overwrite) await w();
|
||||
else throw new Error("Room already exists");
|
||||
@@ -117,7 +161,7 @@ export class RoomFactory {
|
||||
const galvTags = this.getGalvanicTags();
|
||||
|
||||
const subroomExports = (await Promise.all(
|
||||
this.getSubrooms().map(subroom => this.getSubroom(subroom))
|
||||
this.getSubrooms().values().map(subroom => this.getSubroom(subroom))
|
||||
)).map(factory => factory.export());
|
||||
|
||||
return {
|
||||
@@ -154,14 +198,36 @@ export class RoomFactory {
|
||||
VisitCount: await this.getVisitCount()
|
||||
}
|
||||
}
|
||||
setRoomProperties(props: RoomDataTypes.DatabaseRoomContent) {
|
||||
Object.assign(this, props);
|
||||
}
|
||||
|
||||
getRoomId() {
|
||||
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
|
||||
return this.#roomId;
|
||||
}
|
||||
|
||||
getSubrooms() {
|
||||
if (!this.#obj) throw this.#cannotAccessBeforeInitError;
|
||||
return this.#obj.Subrooms;
|
||||
if (!this.#subrooms) throw this.#cannotAccessBeforeInitError;
|
||||
return this.#subrooms;
|
||||
}
|
||||
async getSubroom(id: number) {
|
||||
if (!this.#subrooms) throw this.#cannotAccessBeforeInitError;
|
||||
if (!this.#subrooms.has(id)) throw new Error("Subroom not available to this room");
|
||||
return await new SubroomFactory(this.#server, this.#kv).init({ mode: FactoryMode.Fetch, writeMode: WriteMode.WriteIfFree, id });
|
||||
}
|
||||
getAvailableSubroomId() {
|
||||
let id = Math.round(Math.random() * Math.pow(2, 31));
|
||||
if (this.getSubrooms().has(id)) id = this.getAvailableSubroomId();
|
||||
return id;
|
||||
}
|
||||
async newSubroom(mode?: WriteMode) {
|
||||
return await new SubroomFactory(this.#server, this.#kv).init({ mode: FactoryMode.Write, writeMode: mode ? mode : WriteMode.WriteIfFree, id: this.getAvailableSubroomId() });
|
||||
}
|
||||
addSubroom(id: number) {
|
||||
if (!this.#subrooms) throw this.#cannotAccessBeforeInitError;
|
||||
this.#subrooms.add(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 }
|
||||
@@ -178,8 +244,8 @@ export class RoomFactory {
|
||||
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 Accessibility() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Accessibility }
|
||||
set Accessibility(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 }
|
||||
@@ -229,6 +295,20 @@ export class RoomFactory {
|
||||
this.#hardwareSupport.delete(hardware);
|
||||
await this.#saveHardwareSupport();
|
||||
}
|
||||
/**
|
||||
* Removes support for every hardware type in the room. When saving immediately after this, no client can legally join the room.
|
||||
*
|
||||
* In production, ensure at least one hardware type is supported.
|
||||
*/
|
||||
removeAllHardwareSupport() {
|
||||
this.#hardwareSupport = new Set();
|
||||
}
|
||||
/**
|
||||
* Adds support for every hardware type in the room.
|
||||
*/
|
||||
addAllHardwareSupport() {
|
||||
this.#hardwareSupport = new Set(HardwareSupports);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits are fetched during every access, not during init
|
||||
@@ -252,8 +332,8 @@ export class RoomFactory {
|
||||
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 });
|
||||
if (this.IsDormRoom) tags.push({ Tag: "dormroom", Type: TagType.AGOnly });
|
||||
if (this.Name === "Paintball" || this.Name === "PaintballVR") tags.push({ Tag: "paintball", Type: TagType.AGOnly });
|
||||
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 });
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
import type KV from "../../persistence/kv.ts";
|
||||
import { type ServerBase } from "../../server.ts";
|
||||
import { DatabaseSubroom, FactoryMode, RoomDataTypes, RoomSave, WriteMode } from "./RoomDataTypes.ts";
|
||||
import { DatabaseSubroom, FactoryMode, RoomDataTypes, RoomSave, RoomSaveMap, WriteMode } from "./RoomDataTypes.ts";
|
||||
|
||||
export interface SubroomFactoryOptions {
|
||||
mode: FactoryMode,
|
||||
@@ -8,6 +9,8 @@ export interface SubroomFactoryOptions {
|
||||
id: number
|
||||
}
|
||||
|
||||
const log = new Logging("SubroomFactoryBase");
|
||||
|
||||
export class SubroomFactory {
|
||||
|
||||
#server: ServerBase;
|
||||
@@ -19,7 +22,7 @@ export class SubroomFactory {
|
||||
writeMode: WriteMode = WriteMode.WriteIfFree;
|
||||
|
||||
#obj: DatabaseSubroom | null = null;
|
||||
#saves: RoomSave[] | null = null;
|
||||
#saves: RoomSaveMap | null = null;
|
||||
|
||||
#cannotAccessBeforeInitError = new Error("Cannot access properties before initialization");
|
||||
#cannotWriteBeforeInitError = new Error("Cannot write before initialization");
|
||||
@@ -32,13 +35,22 @@ export class SubroomFactory {
|
||||
}
|
||||
|
||||
async init(options: SubroomFactoryOptions) {
|
||||
|
||||
const data = await this.#kv.getKv().get<DatabaseSubroom>([options.id, 'meta']);
|
||||
if (data == null && this.factoryMode == FactoryMode.Fetch) throw new Error("No such subroom");
|
||||
|
||||
const saves = await this.#kv.getKv().get<RoomSave[]>([options.id, 'saves']);
|
||||
this.#saves = saves.value;
|
||||
|
||||
this.#obj = data.value;
|
||||
if (options.mode == FactoryMode.Fetch && data == null) throw new Error("No such subroom");
|
||||
|
||||
const saves = await this.#kv.getKv().get<RoomSaveMap>([options.id, 'saves']);
|
||||
this.#saves = saves.value ?? new Map<number, RoomSave>();
|
||||
|
||||
this.#obj = options.mode == FactoryMode.Fetch ? data.value : {
|
||||
RoomId: 0,
|
||||
RoomSceneLocationId: "",
|
||||
Name: "Subroom data init failed, contact an admin!",
|
||||
IsSandbox: false,
|
||||
MaxPlayers: 8,
|
||||
CanMatchmakeInto: true,
|
||||
LatestSaveId: null
|
||||
}; // use template object when writing
|
||||
this.#subroomId = options.id;
|
||||
|
||||
return this;
|
||||
@@ -66,7 +78,7 @@ export class SubroomFactory {
|
||||
IsSandbox: this.IsSandbox,
|
||||
MaxPlayers: this.MaxPlayers,
|
||||
CanMatchmakeInto: this.CanMatchmakeInto,
|
||||
DataModifiedAt: save.SavedAt,
|
||||
DataModifiedAt: save.SavedAt.toISOString(),
|
||||
DataBlobName: save.DataBlobName
|
||||
}
|
||||
}
|
||||
@@ -98,24 +110,41 @@ export class SubroomFactory {
|
||||
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
|
||||
|
||||
let newId = Math.round(Math.random() * Math.pow(2, 31));
|
||||
while (this.#saves.some(save => save.SaveId == newId)) newId = this.#getAvailableSaveId();
|
||||
while (this.#saves.has(newId)) newId = this.#getAvailableSaveId();
|
||||
return newId;
|
||||
}
|
||||
addSave(dataBlobName: string) {
|
||||
/**
|
||||
* Add a save (history) to the scene. Automatically resets the LatestSaveId.
|
||||
*
|
||||
* Use empty string for no datablob
|
||||
*
|
||||
* @param dataBlobName Filename of datablob on CDN
|
||||
*/
|
||||
addSave(dataBlobName?: string) {
|
||||
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
|
||||
this.#saves.push({
|
||||
SaveId: this.#getAvailableSaveId(),
|
||||
DataBlobName: dataBlobName,
|
||||
SavedAt: new Date().toISOString()
|
||||
|
||||
const newId = this.#getAvailableSaveId();
|
||||
this.#saves.set(newId, {
|
||||
DataBlobName: dataBlobName ?? "",
|
||||
SavedAt: new Date()
|
||||
});
|
||||
|
||||
this.LatestSaveId = newId;
|
||||
}
|
||||
getSaves() {
|
||||
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
|
||||
return this.#saves;
|
||||
}
|
||||
getLatestSave() {
|
||||
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
|
||||
return this.#saves.find(save => save.SaveId == this.LatestSaveId);
|
||||
if (!this.#saves || !this.#obj) throw this.#cannotAccessBeforeInitError;
|
||||
else if (!this.#obj.LatestSaveId) throw new Error(`No save is marked as the latest save`);
|
||||
else {
|
||||
if (this.#saves.size === 0) {
|
||||
log.w(`No save could be found when fetching the latest save for subroomid ${this.#subroomId}!`);
|
||||
return null;
|
||||
} else if (this.#saves.size === 1) return this.#saves.values().toArray()[0];
|
||||
else return this.#saves.get(this.#obj.LatestSaveId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user