Files
galvanic-corrosion-rewrite/src/server/rooms/internal/SubroomFactory.ts

154 lines
6.1 KiB
TypeScript

import Logging from "@proxnet/undead-logging";
import type KV from "../../persistence/kv.ts";
import { type ServerBase } from "../../server.ts";
import { DatabaseSubroom, FactoryMode, RoomDataTypes, RoomSave, RoomSaveMap, WriteMode } from "./RoomDataTypes.ts";
export interface SubroomFactoryOptions {
mode: FactoryMode,
writeMode: WriteMode
id: number
}
const log = new Logging("SubroomFactoryBase");
export class SubroomFactory {
#server: ServerBase;
#kv: KV;
#subroomId: number | null = null;
factoryMode: FactoryMode = FactoryMode.Fetch;
writeMode: WriteMode = WriteMode.WriteIfFree;
#obj: DatabaseSubroom | null = null;
#saves: RoomSaveMap | 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: SubroomFactoryOptions) {
const data = await this.#kv.getKv().get<DatabaseSubroom>([options.id, 'meta']);
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;
}
async write() {
if (!this.#obj) throw this.#cannotWriteBeforeInitError;
if (!this.#subroomId) throw new Error("No RoomSceneId set");
await this.#kv.getKv().set([this.#subroomId, 'meta'], this.#obj);
await this.#kv.getKv().set([this.#subroomId, 'saves'], this.#saves);
this.#server.emit('room.subroom.updated', { subroom: this });
}
export(): RoomDataTypes.RoomScene {
if (!this.#subroomId) throw this.#cannotAccessBeforeInitError;
const save = this.getLatestSave();
if (!save) throw new Error("No save to export");
return {
RoomId: this.RoomId,
RoomSceneId: this.#subroomId,
RoomSceneLocationId: this.RoomSceneLocationId,
Name: this.Name,
IsSandbox: this.IsSandbox,
MaxPlayers: this.MaxPlayers,
CanMatchmakeInto: this.CanMatchmakeInto,
DataModifiedAt: save.SavedAt.toISOString(),
DataBlobName: save.DataBlobName
}
}
setSubroomProperties(props: RoomDataTypes.SubroomProps) {
Object.assign(this, props);
}
get RoomId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.RoomId; }
set RoomId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.RoomId = data }
get RoomSceneId() { if (!this.#subroomId) throw this.#cannotAccessBeforeInitError; else return this.#subroomId; }
get RoomSceneLocationId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.RoomSceneLocationId; }
set RoomSceneLocationId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.RoomSceneLocationId = data }
get Name() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Name; }
set Name(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.Name = data }
get IsSandbox() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.IsSandbox; }
set IsSandbox(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.IsSandbox = data }
get MaxPlayers() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.MaxPlayers; }
set MaxPlayers(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.MaxPlayers = data }
get CanMatchmakeInto() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.CanMatchmakeInto; }
set CanMatchmakeInto(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.CanMatchmakeInto = data }
get LatestSaveId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.LatestSaveId; }
set LatestSaveId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.LatestSaveId = data }
#getAvailableSaveId() {
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
let newId = Math.round(Math.random() * Math.pow(2, 31));
while (this.#saves.has(newId)) newId = this.#getAvailableSaveId();
return newId;
}
/**
* 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;
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 || !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);
}
}
}