diff --git a/src/data/live/instances.ts b/src/data/live/instances.ts index d02d4e4..3b5ce48 100644 --- a/src/data/live/instances.ts +++ b/src/data/live/instances.ts @@ -1,5 +1,8 @@ +import Logging from "@proxnet/undead-logging"; import Profile from "../profiles.ts"; +const log = new Logging("Instances"); + enum IntegratedRoomScene { Calibration = "f5fbd9c9-e853-4036-9d48-5f68e861af04", DormRoom = "76d98498-60a1-430c-ab76-b54a29b7a163", @@ -48,7 +51,7 @@ enum IntegratedRoomScene { DodgeballVR = "3d474b26-26f7-45e9-9a36-9b02847d5e6f", } -interface RoomInstance { +export interface RoomInstance { roomInstanceId: number, roomId: number, @@ -67,21 +70,98 @@ interface RoomInstance { } const instancePlayers: Map> = new Map(); +/** + * `Map` + */ const instanceMap: Map> = new Map(); class InstancesBase { - async #ensureSubSet(roomId: number) { - const subMap = instanceMap.get(roomId); - if (!subMap) instanceMap.set(roomId, new Set()); + getAllInstances() { + return new Set([...instanceMap.values()].flatMap(set => [...set])); } - // get all instances - // get all instances for a room - // do not put instance categorization here (instance searching, or "matchmaking"); put that in MatchmakingBase + getAllRoomInstances(roomId: number) { + let instances = instanceMap.get(roomId); + if (!instances) { + instances = new Set(); + instanceMap.set(roomId, instances); + } + return instances; + } + + getInstancePlayers(instance: RoomInstance): Set { + let players = instancePlayers.get(instance); + if (!players) { + players = new Set(); + instancePlayers.set(instance, players); + } + return players; + } + + clearEmptyInstances(instances: Set, roomId?: number) { + const beforeCount = instances.size; + for (const instance of instances) { + const profiles = this.getInstancePlayers(instance); + if (profiles.size === 0) { + log.i(`Instance ${instance.roomInstanceId} empty, deleting`); + instancePlayers.delete(instance); + this.getAllRoomInstances(instance.roomId).delete(instance); + } + } + + const afterCount = instances.size; + log.d(`Cleared ${roomId !== undefined ? `room ${roomId}` : 'all'} empty instances.\n Instances before: ${beforeCount}\n Instances after: ${afterCount}`); + } + + clearAllEmptyInstances() { + this.clearEmptyInstances(this.getAllInstances()); + } + + clearAllRoomEmptyInstances(roomId: number) { + this.clearEmptyInstances(this.getAllRoomInstances(roomId), roomId); + } + + updateGlobalInstancesIsFull() { + for (const instance of this.getAllInstances()) { + instance.isFull = this.getInstancePlayers(instance).size >= instance.maxCapacity; + } + } + + updateSingleInstanceIsFull(instance: RoomInstance) { + const profiles = this.getInstancePlayers(instance); + if (profiles.size >= instance.maxCapacity) instance.isFull = true; + else instance.isFull = false; + } + + instanceCanFitPlayer(instance: RoomInstance) { + return this.getInstancePlayers(instance).size < instance.maxCapacity; + } // add, remove, check for, get profile(s) in instances // synchronize profile.#instance with the profile's current instance (profile.setInstance(RoomInstance)?) + setPlayerInstance(player: Profile, instance: RoomInstance) { + const currentInstance = player.getInstance(); + if (currentInstance === instance) return; + + if (currentInstance) { + this.getInstancePlayers(currentInstance).delete(player); + this.updateSingleInstanceIsFull(currentInstance); + } + + if (this.instanceCanFitPlayer(instance)) { + this.getInstancePlayers(instance).add(player); + player.setInstance(instance); + this.updateSingleInstanceIsFull(instance); + } else log.w(`Instance ${instance.roomInstanceId} is full. Cannot add player ${player.getId()}`); + } + + playerIsInInstance(player: Profile, instance: RoomInstance) { + + const profiles = this.getInstancePlayers(instance); + return profiles.has(player); + + } } diff --git a/src/data/profiles.ts b/src/data/profiles.ts index b13c5cf..e5e1c0e 100644 --- a/src/data/profiles.ts +++ b/src/data/profiles.ts @@ -4,6 +4,7 @@ import { Config } from "../config.ts"; import { AuthType } from "./users.ts"; import * as JsonWebToken from "@gz/jwt"; import { TokenBaseFormat } from "../apiutils.ts"; +import { RoomInstance } from "./live/instances.ts"; const config = Config.getConfig(); @@ -180,15 +181,25 @@ class Profile { #id: number; + #instance: RoomInstance | null = null; + constructor(id: number) { this.#id = id; } + setInstance(instance: RoomInstance) { + this.#instance = instance; + } + + getInstance() { + return this.#instance; + } + getId() { return this.#id; } - async getIsDev() { + async getIsOperator() { return (await Redis.Database.sismember(Redis.buildKey(Redis.KeyGroups.Operators), this.#id.toString())) >= 1; } @@ -203,7 +214,7 @@ class Profile { const payload: ProfileTokenFormat = { iss: config.web.publichost, sub: this.#id, - role: (await this.getIsDev()) ? 'developer' : 'user', + role: (await this.getIsOperator()) ? 'developer' : 'user', exp: Math.round(Date.now() / 1000) + Math.round(config.auth.timeout * 60 * 60), typ: AuthType.Game };