163 lines
4.7 KiB
TypeScript
163 lines
4.7 KiB
TypeScript
import Logging from "@proxnet/undead-logging";
|
|
import { ServerContentBase } from "../ContentBase.ts";
|
|
import { DeviceClass } from "../platforms/types.ts";
|
|
import Profile from "../profiles/profile.ts";
|
|
import { type ServerBase } from "../server.ts";
|
|
import { RoomInstance } from "../instances/types.ts";
|
|
import Command from "../commands/command.ts";
|
|
import z from "zod";
|
|
import { PushNotificationId } from "../socket/signalr/types.ts";
|
|
|
|
const log = new Logging("Presence");
|
|
|
|
export enum VRMovementMode {
|
|
TELEPORT,
|
|
WALK
|
|
}
|
|
|
|
export enum PlayerStatusVisibility {
|
|
Public,
|
|
FriendsOnly,
|
|
FavoriteFriendsOnly,
|
|
Offline
|
|
}
|
|
|
|
export interface PresenceExport {
|
|
playerId: number,
|
|
statusVisibility: PlayerStatusVisibility,
|
|
deviceClass: DeviceClass,
|
|
vrMovementMode?: VRMovementMode,
|
|
roomInstance: RoomInstance | null
|
|
}
|
|
|
|
export class Presence {
|
|
|
|
#server: ServerBase;
|
|
|
|
#profile: Profile;
|
|
|
|
#statusVisibility: PlayerStatusVisibility = PlayerStatusVisibility.Offline;
|
|
#deviceClass: DeviceClass = DeviceClass.Unknown;
|
|
#vrMovementMove: VRMovementMode | undefined;
|
|
|
|
#lastExported: Date = new Date();
|
|
|
|
constructor(profile: Profile, server: ServerBase) {
|
|
this.#profile = profile;
|
|
this.#server = server;
|
|
}
|
|
|
|
/** Refer to `Profile.Matchmaking.updateLastSeen` */
|
|
updateLastSeen() {
|
|
this.#profile.Matchmaking.updateLastSeen();
|
|
}
|
|
|
|
async update() {
|
|
this.#deviceClass = (await this.#profile.Matchmaking.getLastDeviceClass()) || DeviceClass.Unknown;
|
|
const isOnline = (Date.now() - (await this.#profile.Matchmaking.getLastSeen()).getTime()) < 90_000;
|
|
this.#statusVisibility =
|
|
isOnline ?
|
|
this.#statusVisibility :
|
|
PlayerStatusVisibility.Offline;
|
|
|
|
this.#server.emit('presence.update', { profile: this.#profile, presence: this });
|
|
}
|
|
|
|
setStatusVisibility(sv: PlayerStatusVisibility) {
|
|
this.#statusVisibility = sv;
|
|
this.updateLastSeen();
|
|
this.update();
|
|
|
|
this.#server.emit('presence.update', { profile: this.#profile, presence: this });
|
|
}
|
|
|
|
setVRMovementMode(mm: VRMovementMode) {
|
|
this.#vrMovementMove = mm;
|
|
this.updateLastSeen();
|
|
this.update();
|
|
|
|
this.#server.emit('presence.update', { profile: this.#profile, presence: this });
|
|
}
|
|
|
|
getLastExported() {
|
|
return this.#lastExported;
|
|
}
|
|
|
|
logout() {
|
|
this.updateLastSeen();
|
|
this.#statusVisibility = PlayerStatusVisibility.Offline;
|
|
}
|
|
|
|
export() {
|
|
log.d(`profId ${this.#profile.getId()} presence exported. has instance: ${this.#profile.getInstance() !== null}`);
|
|
|
|
this.#lastExported = new Date();
|
|
const e: PresenceExport = {
|
|
playerId: this.#profile.getId(),
|
|
statusVisibility: this.#statusVisibility,
|
|
deviceClass: this.#deviceClass,
|
|
vrMovementMode: this.#vrMovementMove,
|
|
roomInstance: this.#profile.getInstance()?.export() ?? null
|
|
}
|
|
return e;
|
|
}
|
|
|
|
}
|
|
|
|
export class ServerPresenceBase extends ServerContentBase {
|
|
|
|
#log = new Logging("Presence");
|
|
|
|
#presenceMap: Map<Profile, Presence> = new Map();
|
|
|
|
#intervalId?: number;
|
|
|
|
#deleteDeadPresences() {
|
|
for (const pres of this.#presenceMap.values()) {
|
|
if (Date.now() - pres.getLastExported().getTime() > 300_000) pres
|
|
}
|
|
}
|
|
|
|
getPresence(profile: Profile) {
|
|
let pres = this.#presenceMap.get(profile);
|
|
if (!pres) {
|
|
pres = new Presence(profile, this.server);
|
|
this.#presenceMap.set(profile, pres);
|
|
}
|
|
return pres;
|
|
}
|
|
|
|
override start() {
|
|
this.#intervalId = setInterval(() => {
|
|
if (this.#presenceMap.size === 0) return;
|
|
|
|
this.#deleteDeadPresences();
|
|
}, 300_000);
|
|
|
|
this.server.Commands.addRootCommand(new Command({
|
|
key: ["presence", "pres"],
|
|
subcommands: [
|
|
new Command({
|
|
key: ["quit", "quitgame"],
|
|
exec: async (pid: number) => {
|
|
const prof = await this.server.Profiles.get(pid);
|
|
if (!prof) return false;
|
|
|
|
const socket = prof.getSocketHandler();
|
|
if (!socket) return false;
|
|
|
|
socket.sendNotification(PushNotificationId.ModerationQuitGame);
|
|
return true;
|
|
},
|
|
zod: z.tuple([z.coerce.number()]),
|
|
help: "Sends ModerationQuitGame to a player's socket if it is connected. Returns true if successful."
|
|
})
|
|
]
|
|
}));
|
|
}
|
|
|
|
override destroy() {
|
|
clearInterval(this.#intervalId ?? undefined);
|
|
}
|
|
|
|
} |