diff --git a/server/BanchoServer.ts b/server/BanchoServer.ts index dc7b06a..5abf090 100644 --- a/server/BanchoServer.ts +++ b/server/BanchoServer.ts @@ -3,6 +3,7 @@ import { ConsoleHelper } from "../ConsoleHelper"; import { Channel } from "./objects/Channel"; import { ChatManager } from "./ChatManager"; import { Database } from "./objects/Database"; +import { DataStreamArray } from "./objects/DataStreamArray"; import { LatLng } from "./objects/LatLng"; import { LoginProcess } from "./LoginProcess"; import { Packets } from "./enums/Packets"; @@ -10,17 +11,15 @@ import { replaceAll } from "./Util"; import { readFileSync } from "fs"; import { RedisClientType, createClient } from "redis"; import { Request, Response } from "express"; +import { SpectatorManager } from "./SpectatorManager"; import { UserArray } from "./objects/UserArray"; import { User } from "./objects/User"; -import { DataStreamArray } from "./objects/DataStreamArray"; import { MultiplayerManager } from "./MultiplayerManager"; import { SharedContent } from "./interfaces/SharedContent"; const config:Config = JSON.parse(readFileSync("./config.json").toString()) as Config; // TODO: Port osu-packet to TypeScript const osu = require("osu-packet"); - - const sharedContent:any = {}; // NOTE: This function should only be used externaly in Binato.ts and in this file. export function GetSharedContent() : SharedContent { @@ -53,6 +52,8 @@ chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese"); const multiplayerManager:MultiplayerManager = sharedContent.mutiplayerManager = new MultiplayerManager(GetSharedContent()); +const spectatorManager:SpectatorManager = new SpectatorManager(GetSharedContent()); + let redisClient:RedisClientType; async function subscribeToChannel(channelName:string, callback:(message:string) => void) { @@ -97,7 +98,6 @@ import { Logout } from "./packets/Logout"; import { UserPresence } from "./packets/UserPresence"; import { UserStatsRequest } from "./packets/UserStatsRequest"; import { UserPresenceBundle } from "./packets/UserPresenceBundle"; -import { Match } from "./objects/Match"; // User timeout interval setInterval(() => { @@ -171,15 +171,15 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) { break; case Packets.Client_StartSpectating: - //Spectator.startSpectatingUser(PacketUser, CurrentPacket.data); + spectatorManager.startSpectating(PacketUser, CurrentPacket.data); break; case Packets.Client_SpectateFrames: - //Spectator.sendSpectatorFrames(PacketUser, CurrentPacket.data); + spectatorManager.spectatorFrames(PacketUser, CurrentPacket.data); break; case Packets.Client_StopSpectating: - //Spectator.stopSpectatingUser(PacketUser); + spectatorManager.stopSpectating(PacketUser); break; case Packets.Client_SendPrivateMessage: diff --git a/server/SpectatorManager.ts b/server/SpectatorManager.ts new file mode 100644 index 0000000..f74da87 --- /dev/null +++ b/server/SpectatorManager.ts @@ -0,0 +1,79 @@ +import { DataStream } from "./objects/DataStream"; +import { SharedContent } from "./interfaces/SharedContent"; +import { User } from "./objects/User"; +const osu = require("osu-packet"); + +export class SpectatorManager { + private sharedContent:SharedContent; + + public constructor(sharedContent:SharedContent) { + this.sharedContent = sharedContent; + } + + public startSpectating(user:User, userIdToSpectate:number) { + const userToSpectate = this.sharedContent.users.getById(userIdToSpectate); + if (userToSpectate === undefined) { + return; + } + + // Use existing or create spectator stream + let spectateStream:DataStream; + if (userToSpectate.spectatorStream === undefined) { + user.spectatorStream = spectateStream = userToSpectate.spectatorStream = this.sharedContent.streams.CreateStream(`spectator:${userToSpectate.username}`); + } else { + user.spectatorStream = spectateStream = userToSpectate.spectatorStream; + } + + user.spectatingUser = userToSpectate; + + let osuPacketWriter = new osu.Bancho.Writer; + + osuPacketWriter.SpectatorJoined(user.id); + + userToSpectate.addActionToQueue(osuPacketWriter.toBuffer); + + osuPacketWriter = new osu.Bancho.Writer; + + osuPacketWriter.FellowSpectatorJoined(user.id); + + spectateStream.Send(osuPacketWriter.toBuffer); + } + + // TODO: Interface for spectateFrameData + public spectatorFrames(user:User, spectateFrameData:any) { + if (user.spectatorStream === undefined) { + return; + } + + const osuPacketWriter = new osu.Bancho.Writer; + osuPacketWriter.SpectateFrames(spectateFrameData); + + user.spectatorStream.Send(osuPacketWriter.toBuffer); + } + + public stopSpectating(user:User) { + if (user.spectatingUser === undefined || user.spectatorStream === undefined) { + return; + } + + const spectatedUser = user.spectatingUser; + + let osuPacketWriter = new osu.Bancho.Writer; + osuPacketWriter.SpectatorLeft(user.id); + spectatedUser.addActionToQueue(osuPacketWriter.toBuffer); + + const stream = user.spectatorStream; + + stream.RemoveUser(user); + user.spectatorStream = undefined; + user.spectatingUser = undefined; + + if (stream.IsActive) { + osuPacketWriter = new osu.Bancho.Writer; + osuPacketWriter.FellowSpectatorLeft(user.id); + stream.Send(osuPacketWriter.toBuffer); + } else { + spectatedUser.spectatorStream = undefined; + } + } +} \ No newline at end of file diff --git a/server/objects/DataStream.ts b/server/objects/DataStream.ts index caef15d..4b37541 100644 --- a/server/objects/DataStream.ts +++ b/server/objects/DataStream.ts @@ -18,6 +18,10 @@ export class DataStream { this.removeWhenEmpty = removeWhenEmpty; } + public get IsActive() : boolean { + return this.inactive; + } + private checkInactive() { if (this.inactive) { throw `Stream ${this.name} is inactive (deleted) and cannot be used here.`; diff --git a/server/objects/User.ts b/server/objects/User.ts index 1741aa7..e975ee7 100644 --- a/server/objects/User.ts +++ b/server/objects/User.ts @@ -26,13 +26,12 @@ export class User { // Binato data public rankingMode:RankingModes = RankingModes.PP; - public spectatorStream:DataStream | null = null; + public spectatorStream?:DataStream; + public spectatingUser?:User; // osu! data public playMode:number = 0; public countryID:number = 0; - //public spectators:Array; // TODO: Figure out if this was ever needed - public spectating:number = -1; public location:LatLng = new LatLng(0, 0); public joinedChannels:Array = new Array();