From f08e34dc82058d60da15bcb6744aa8afebe73120 Mon Sep 17 00:00:00 2001 From: Holly Date: Wed, 23 Nov 2022 00:48:28 +0000 Subject: [PATCH] MULTIPLAYER WORKS KINDA! --- Binato.ts | 3 +- Constants.ts | 2 +- server/BanchoServer.ts | 62 ++- server/ChatManager.ts | 16 +- server/LoginProcess.ts | 2 +- server/MultiplayerManager.ts | 91 ++++- server/enums/Mods.ts | 2 +- server/enums/SlotStatus.ts | 11 +- server/enums/Team.ts | 4 + server/interfaces/MatchData.ts | 20 + server/interfaces/MatchDataSlot.ts | 6 + server/interfaces/MatchJoinData.ts | 4 + server/interfaces/MatchStartSkipData.ts | 4 + server/interfaces/PlayerScore.ts | 11 + server/interfaces/SharedContent.ts | 13 + server/objects/Channel.ts | 36 +- server/objects/DataStream.ts | 2 +- server/objects/Match.ts | 497 +++++++++++++----------- server/objects/MatchArray.ts | 14 + server/objects/Slot.ts | 38 +- server/objects/User.ts | 15 +- server/packets/StatusUpdate.ts | 2 +- server/packets/UserPresence.ts | 2 +- server/packets/UserPresenceBundle.ts | 2 +- 24 files changed, 554 insertions(+), 305 deletions(-) create mode 100644 server/enums/Team.ts create mode 100644 server/interfaces/MatchData.ts create mode 100644 server/interfaces/MatchDataSlot.ts create mode 100644 server/interfaces/MatchJoinData.ts create mode 100644 server/interfaces/MatchStartSkipData.ts create mode 100644 server/interfaces/PlayerScore.ts create mode 100644 server/interfaces/SharedContent.ts create mode 100644 server/objects/MatchArray.ts diff --git a/Binato.ts b/Binato.ts index 0f65ade..d47715c 100644 --- a/Binato.ts +++ b/Binato.ts @@ -11,7 +11,8 @@ if (!existsSync("./config.json")) { import { ChatHistory } from "./server/ChatHistory"; import compression from "compression"; import express from "express"; -import { HandleRequest, GetSharedContent, SharedContent } from "./server/BanchoServer"; +import { HandleRequest, GetSharedContent } from "./server/BanchoServer"; +import { SharedContent } from "./server/interfaces/SharedContent"; import { Registry, collectDefaultMetrics } from "prom-client"; const config:any = JSON.parse(readFileSync(__dirname + "/config.json").toString()); // Pull out shared data from BanchoServer diff --git a/Constants.ts b/Constants.ts index 9618fad..cd6064d 100644 --- a/Constants.ts +++ b/Constants.ts @@ -1,3 +1,3 @@ export abstract class Constants { - public static readonly DEBUG = true; + public static readonly DEBUG = false; } \ No newline at end of file diff --git a/server/BanchoServer.ts b/server/BanchoServer.ts index 085311a..1cefd5e 100644 --- a/server/BanchoServer.ts +++ b/server/BanchoServer.ts @@ -13,17 +13,12 @@ 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:any = JSON.parse(readFileSync("./config.json").toString()); // TODO: Port osu-packet to TypeScript const osu = require("osu-packet"); -export interface SharedContent { - chatManager:ChatManager, - database:Database, - mutiplayerManager:MultiplayerManager, - streams:DataStreamArray, - users:UserArray, -} + const sharedContent:any = {}; // NOTE: This function should only be used externaly in Binato.ts and in this file. @@ -40,11 +35,16 @@ const DB:Database = sharedContent.database = new Database(config.database.addres // User session storage const users:UserArray = sharedContent.users = new UserArray(); +// Add the bot user +const botUser:User = users.add("bot", new User(3, "SillyBot", "bot", GetSharedContent())); +// Set the bot's position on the map +botUser.location = new LatLng(50, -32); + // DataStream storage const streams:DataStreamArray = sharedContent.streams = new DataStreamArray(); // ChatManager -const chatManager:ChatManager = sharedContent.chatManager = new ChatManager(streams); +const chatManager:ChatManager = sharedContent.chatManager = new ChatManager(GetSharedContent()); chatManager.AddChatChannel("osu", "The main channel", true); chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff"); chatManager.AddChatChannel("english", "Talk in exclusively English"); @@ -52,11 +52,6 @@ chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese"); const multiplayerManager:MultiplayerManager = sharedContent.mutiplayerManager = new MultiplayerManager(GetSharedContent()); -// Add the bot user -const botUser:User = users.add("bot", new User(3, "SillyBot", "bot", GetSharedContent())); -// Set the bot's position on the map -botUser.location = new LatLng(50, -32); - let redisClient:RedisClientType; async function subscribeToChannel(channelName:string, callback:(message:string) => void) { @@ -101,6 +96,7 @@ 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(() => { @@ -190,11 +186,11 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) { break; case Packets.Client_JoinLobby: - //multiplayerManager.userEnterLobby(PacketUser); + multiplayerManager.JoinLobby(PacketUser); break; case Packets.Client_PartLobby: - //multiplayerManager.userLeaveLobby(PacketUser); + multiplayerManager.LeaveLobby(PacketUser); break; case Packets.Client_CreateMatch: @@ -202,76 +198,76 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) { break; case Packets.Client_JoinMatch: - //multiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data); + multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data); break; case Packets.Client_MatchChangeSlot: - //PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data); + PacketUser.match?.moveToSlot(PacketUser, CurrentPacket.data); break; case Packets.Client_MatchReady: - //PacketUser.currentMatch.setStateReady(PacketUser); + PacketUser.match?.setStateReady(PacketUser); break; case Packets.Client_MatchChangeSettings: - //await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data); + await PacketUser.match?.updateMatch(PacketUser, CurrentPacket.data); break; case Packets.Client_MatchNotReady: - //PacketUser.currentMatch.setStateNotReady(PacketUser); + PacketUser.match?.setStateNotReady(PacketUser); break; + // TODO: Match leave so the matches actually close case Packets.Client_PartMatch: //await multiplayerManager.leaveMultiplayerMatch(PacketUser); break; - // Also handles user kick if the slot has a user case Packets.Client_MatchLock: - //PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data); + PacketUser.match?.lockOrKick(PacketUser, CurrentPacket.data); break; case Packets.Client_MatchNoBeatmap: - //PacketUser.currentMatch.missingBeatmap(PacketUser); + PacketUser.match?.missingBeatmap(PacketUser); break; case Packets.Client_MatchSkipRequest: - //PacketUser.currentMatch.matchSkip(PacketUser); + PacketUser.match?.matchSkip(PacketUser); break; case Packets.Client_MatchHasBeatmap: - //PacketUser.currentMatch.notMissingBeatmap(PacketUser); + PacketUser.match?.notMissingBeatmap(PacketUser); break; case Packets.Client_MatchTransferHost: - //PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data); + PacketUser.match?.transferHost(PacketUser, CurrentPacket.data); break; case Packets.Client_MatchChangeMods: - //PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data); + PacketUser.match?.updateMods(PacketUser, CurrentPacket.data); break; case Packets.Client_MatchStart: - //PacketUser.currentMatch.startMatch(); + PacketUser.match?.startMatch(); break; case Packets.Client_MatchLoadComplete: - //PacketUser.currentMatch.matchPlayerLoaded(PacketUser); + PacketUser.match?.matchPlayerLoaded(PacketUser); break; case Packets.Client_MatchComplete: - //await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser); + await PacketUser.match?.onPlayerFinishMatch(PacketUser); break; case Packets.Client_MatchScoreUpdate: - //PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data); + PacketUser.match?.updatePlayerScore(PacketUser, CurrentPacket.data); break; case Packets.Client_MatchFailed: - //PacketUser.currentMatch.matchFailed(PacketUser); + PacketUser.match?.matchFailed(PacketUser); break; case Packets.Client_MatchChangeTeam: - //PacketUser.currentMatch.changeTeam(PacketUser); + PacketUser.match?.changeTeam(PacketUser); break; case Packets.Client_ChannelJoin: diff --git a/server/ChatManager.ts b/server/ChatManager.ts index c6fb84a..7d6b618 100644 --- a/server/ChatManager.ts +++ b/server/ChatManager.ts @@ -1,22 +1,22 @@ import { Channel } from "./objects/Channel"; import { ConsoleHelper } from "../ConsoleHelper"; import { FunkyArray } from "./objects/FunkyArray"; -import { DataStreamArray } from "./objects/DataStreamArray"; import { User } from "./objects/User"; +import { SharedContent } from "./interfaces/SharedContent"; const osu = require("osu-packet"); export class ChatManager { public chatChannels:FunkyArray = new FunkyArray(); public forceJoinChannels:FunkyArray = new FunkyArray(); - public streams:DataStreamArray; + private readonly sharedContent:SharedContent; - public constructor(streams:DataStreamArray) { - this.streams = streams; + public constructor(sharedContent:SharedContent) { + this.sharedContent = sharedContent; } public AddChatChannel(name:string, description:string, forceJoin:boolean = false) : Channel { - const stream = this.streams.CreateStream(`chat_channel:${name}`, false); - const channel = new Channel(`#${name}`, description, stream); + const stream = this.sharedContent.streams.CreateStream(`chat_channel:${name}`, false); + const channel = new Channel(this.sharedContent, `#${name}`, description, stream); this.chatChannels.add(channel.name, channel); if (forceJoin) { this.forceJoinChannels.add(name, channel); @@ -26,8 +26,8 @@ export class ChatManager { } public AddSpecialChatChannel(name:string, streamName:string, forceJoin:boolean = false) : Channel { - const stream = this.streams.CreateStream(`chat_channel:${streamName}`, false); - const channel = new Channel(`#${name}`, "", stream); + const stream = this.sharedContent.streams.CreateStream(`chat_channel:${streamName}`, false); + const channel = new Channel(this.sharedContent, `#${name}`, "", stream); this.chatChannels.add(channel.name, channel); if (forceJoin) { this.forceJoinChannels.add(name, channel); diff --git a/server/LoginProcess.ts b/server/LoginProcess.ts index 3a3d407..b43e893 100644 --- a/server/LoginProcess.ts +++ b/server/LoginProcess.ts @@ -16,7 +16,7 @@ import { ChatManager } from "./ChatManager"; import { UserPresenceBundle } from "./packets/UserPresenceBundle"; import { UserPresence } from "./packets/UserPresence"; import { StatusUpdate } from "./packets/StatusUpdate"; -import { SharedContent } from "./BanchoServer"; +import { SharedContent } from "./interfaces/SharedContent"; const config:any = JSON.parse(readFileSync("./config.json").toString()); const { decrypt: aesDecrypt } = require("aes256"); const osu = require("osu-packet"); diff --git a/server/MultiplayerManager.ts b/server/MultiplayerManager.ts index 19a6c14..734d0e6 100644 --- a/server/MultiplayerManager.ts +++ b/server/MultiplayerManager.ts @@ -1,28 +1,104 @@ -import { SharedContent } from "./BanchoServer"; +import { Channel } from "./objects/Channel"; +import { SharedContent } from "./interfaces/SharedContent"; import { SlotStatus } from "./enums/SlotStatus"; import { DataStream } from "./objects/DataStream"; -import { DataStreamArray } from "./objects/DataStreamArray"; -import { FunkyArray } from "./objects/FunkyArray"; -import { Match, MatchData } from "./objects/Match"; +import { Match } from "./objects/Match"; import { User } from "./objects/User"; import { StatusUpdate } from "./packets/StatusUpdate"; import { UserPresence } from "./packets/UserPresence"; import { UserPresenceBundle } from "./packets/UserPresenceBundle"; +import { MatchArray } from "./objects/MatchArray"; +import { MatchJoinData } from "./interfaces/MatchJoinData"; +import { MatchData } from "./interfaces/MatchData"; const osu = require("osu-packet"); export class MultiplayerManager { private readonly sharedContent:SharedContent; - private matches:FunkyArray = new FunkyArray(); + private matches:MatchArray = new MatchArray(); private readonly lobbyStream:DataStream; + private readonly lobbyChat:Channel; public constructor(sharedContent:SharedContent) { this.sharedContent = sharedContent; this.lobbyStream = sharedContent.streams.CreateStream("multiplayer:lobby", false); + const channel = this.sharedContent.chatManager.GetChannelByName("#lobby"); + if (channel === undefined) { + throw "Something has gone horribly wrong, the lobby channel does not exist!"; + } + this.lobbyChat = channel; } public JoinLobby(user:User) { - if (user.currentMatch != null) { - + if (user.inMatch) { + user.match?.leaveMatch(user); + } + + this.lobbyChat.Join(user); + this.GenerateLobbyListing(user); + this.lobbyStream.AddUser(user); + } + + public LeaveLobby(user:User) { + this.lobbyStream.RemoveUser(user); + } + + public JoinMatch(user:User, matchData:number | MatchJoinData) { + try { + let match:Match | undefined; + if (typeof(matchData) === "number") { + match = this.matches.getById(matchData); + } else { + match = this.matches.getById(matchData.matchId); + } + if (!(match instanceof Match)) { + throw "MatchIdInvalid"; + } + + if (match.gamePassword !== undefined && typeof(matchData) !== "number") { + if (match.gamePassword !== matchData.gamePassword) { + throw "IncorrectPassword"; + } + } + + let matchFull = true; + for (let slot of match.slots) { + if (slot.player instanceof User || slot.status === SlotStatus.Locked) { + continue; + } + + slot.status = SlotStatus.NotReady + slot.player = user; + user.match = match; + user.matchSlot = slot; + matchFull = false; + break; + } + + if (matchFull) { + throw "MatchFull"; + } + + // Inform users in the match that somebody has joined + match.sendMatchUpdate(); + + match.matchStream.AddUser(user); + match.matchChatChannel.Join(user); + + const osuPacketWriter = new osu.Bancho.Writer; + + osuPacketWriter.MatchJoinSuccess(match.generateMatchJSON()); + + user.addActionToQueue(osuPacketWriter.toBuffer); + + this.UpdateLobbyListing(); + } catch (e) { + const osuPacketWriter = new osu.Bancho.Writer; + + osuPacketWriter.MatchJoinFail(); + + user.addActionToQueue(osuPacketWriter.toBuffer); + + this.GenerateLobbyListing(user); } } @@ -61,5 +137,6 @@ export class MultiplayerManager { public async CreateMatch(user:User, matchData:MatchData) { const match = await Match.createMatch(user, matchData, this.sharedContent); this.matches.add(match.matchId.toString(), match); + this.JoinMatch(user, match.matchId); } } \ No newline at end of file diff --git a/server/enums/Mods.ts b/server/enums/Mods.ts index 8923447..4cabd58 100644 --- a/server/enums/Mods.ts +++ b/server/enums/Mods.ts @@ -1,4 +1,4 @@ // TODO: Mods enum export enum Mods { - + None } \ No newline at end of file diff --git a/server/enums/SlotStatus.ts b/server/enums/SlotStatus.ts index 8a8cdb2..a5fcda2 100644 --- a/server/enums/SlotStatus.ts +++ b/server/enums/SlotStatus.ts @@ -1,6 +1,9 @@ export enum SlotStatus { - Unknown_0, - Empty, - Locked, - NotReady, + Empty = 1, + Locked = 2, + NotReady = 4, + Ready = 8, + MissingBeatmap = 16, + Playing = 32, + Quit = 128 } \ No newline at end of file diff --git a/server/enums/Team.ts b/server/enums/Team.ts new file mode 100644 index 0000000..03ce7b1 --- /dev/null +++ b/server/enums/Team.ts @@ -0,0 +1,4 @@ +export enum Team { + Red, + Blue +} \ No newline at end of file diff --git a/server/interfaces/MatchData.ts b/server/interfaces/MatchData.ts new file mode 100644 index 0000000..3f5a6df --- /dev/null +++ b/server/interfaces/MatchData.ts @@ -0,0 +1,20 @@ +import { MatchDataSlot } from "./MatchDataSlot"; + +export interface MatchData { + matchId:number, + matchType:number, + activeMods:number, + gameName:string, + gamePassword:string, + inProgress:boolean, + beatmapName:string, + beatmapId:number, + beatmapChecksum:string, + slots:Array, + host:number, + playMode:number, + matchScoringType:number, + matchTeamType:number, + specialModes:number, + seed:number +} \ No newline at end of file diff --git a/server/interfaces/MatchDataSlot.ts b/server/interfaces/MatchDataSlot.ts new file mode 100644 index 0000000..af75d09 --- /dev/null +++ b/server/interfaces/MatchDataSlot.ts @@ -0,0 +1,6 @@ +export interface MatchDataSlot { + status:number, + team:number, + playerId:number, + mods:number | undefined, +} \ No newline at end of file diff --git a/server/interfaces/MatchJoinData.ts b/server/interfaces/MatchJoinData.ts new file mode 100644 index 0000000..ac5dbed --- /dev/null +++ b/server/interfaces/MatchJoinData.ts @@ -0,0 +1,4 @@ +export interface MatchJoinData { + matchId: number, + gamePassword: string +} \ No newline at end of file diff --git a/server/interfaces/MatchStartSkipData.ts b/server/interfaces/MatchStartSkipData.ts new file mode 100644 index 0000000..1c7ab6d --- /dev/null +++ b/server/interfaces/MatchStartSkipData.ts @@ -0,0 +1,4 @@ +export interface MatchStartSkipData { + playerId:number, + flag:boolean +} \ No newline at end of file diff --git a/server/interfaces/PlayerScore.ts b/server/interfaces/PlayerScore.ts new file mode 100644 index 0000000..1417702 --- /dev/null +++ b/server/interfaces/PlayerScore.ts @@ -0,0 +1,11 @@ +import { Slot } from "../objects/Slot"; +import { User } from "../objects/User"; + +export interface PlayerScore { + player:User, + slot:Slot, + score:number, + isCurrentlyFailed:boolean, + hasFailed:boolean, + _raw:any +} \ No newline at end of file diff --git a/server/interfaces/SharedContent.ts b/server/interfaces/SharedContent.ts new file mode 100644 index 0000000..af7dd3e --- /dev/null +++ b/server/interfaces/SharedContent.ts @@ -0,0 +1,13 @@ +import { ChatManager } from "../ChatManager"; +import { MultiplayerManager } from "../MultiplayerManager"; +import { Database } from "../objects/Database"; +import { DataStreamArray } from "../objects/DataStreamArray"; +import { UserArray } from "../objects/UserArray"; + +export interface SharedContent { + chatManager:ChatManager, + database:Database, + mutiplayerManager:MultiplayerManager, + streams:DataStreamArray, + users:UserArray, +} \ No newline at end of file diff --git a/server/objects/Channel.ts b/server/objects/Channel.ts index 6252879..7987f4c 100644 --- a/server/objects/Channel.ts +++ b/server/objects/Channel.ts @@ -1,3 +1,4 @@ +import { SharedContent } from "../interfaces/SharedContent"; import { DataStream } from "./DataStream"; import { User } from "./User"; const osu = require("osu-packet"); @@ -9,11 +10,23 @@ export class Channel { private isLocked:boolean = false; private _isSpecial:boolean = false; - public constructor(name:string, description:string, stream:DataStream, isSpecial:boolean = false) { + private readonly botUser:User; + + public constructor(sharedContent:SharedContent, name:string, description:string, stream:DataStream, isSpecial:boolean = false) { this.name = name; this.description = description; this.stream = stream; this._isSpecial = isSpecial; + + const bot = sharedContent.users.getByKey("bot"); + if (!(bot instanceof User)) { + throw "Something has gone horribly wrong, the bot user doesn't exist!"; + } + this.botUser = bot; + } + + public get self() { + return this; } public get isSpecial() { @@ -35,6 +48,11 @@ export class Channel { if (message.split(" ")[0] === "!lock") { this.isLocked = true; } + + if (message === "!mp start") { + this.SendBotMessage("glhf!"); + sender.match?.startMatch(); + } } const osuPacketWriter = new osu.Bancho.Writer; @@ -47,6 +65,22 @@ export class Channel { this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender); } + public SendBotMessage(message:string, sendTo?:User) { + const osuPacketWriter = new osu.Bancho.Writer; + osuPacketWriter.SendMessage({ + sendingClient: this.botUser.username, + message: message, + target: this.name, + senderId: this.botUser.id + }); + + if (sendTo instanceof User) { + sendTo.addActionToQueue(osuPacketWriter.toBuffer); + } else { + this.stream.Send(osuPacketWriter.toBuffer); + } + } + public SendSystemMessage(message:string, sendTo?:User) { const osuPacketWriter = new osu.Bancho.Writer; osuPacketWriter.SendMessage({ diff --git a/server/objects/DataStream.ts b/server/objects/DataStream.ts index 29fd5a0..caef15d 100644 --- a/server/objects/DataStream.ts +++ b/server/objects/DataStream.ts @@ -64,7 +64,7 @@ export class DataStream { user.addActionToQueue(data); } if (Constants.DEBUG) { - ConsoleHelper.printStream(`Sent [${data.toString()}] to all users in stream [${this.name}]`); + ConsoleHelper.printStream(`Sent Buffer<${hexlify(data)}> to all users in stream [${this.name}]`); } } diff --git a/server/objects/Match.ts b/server/objects/Match.ts index 49a9303..655dceb 100644 --- a/server/objects/Match.ts +++ b/server/objects/Match.ts @@ -1,38 +1,18 @@ import { Channel } from "./Channel"; -import { SharedContent } from "../BanchoServer"; +import { SharedContent } from "../interfaces/SharedContent"; import { DataStream } from "./DataStream"; import { Slot } from "./Slot"; import { User } from "./User"; import { StatusUpdate } from "../packets/StatusUpdate"; +import { SlotStatus } from "../enums/SlotStatus"; +import { MatchData } from "../interfaces/MatchData"; +import { Team } from "../enums/Team"; +import { MatchStartSkipData } from "../interfaces/MatchStartSkipData"; +import { Mods } from "../enums/Mods"; +import { PlayerScore } from "../interfaces/PlayerScore"; const osu = require("osu-packet"); -export interface MatchData { - matchId:number, - matchType:number, - activeMods:number, - gameName:string, - gamePassword:string, - inProgress:boolean, - beatmapName:string, - beatmapId:number, - beatmapChecksum:string, - slots:Array, - host:number, - playMode:number, - matchScoringType:number, - matchTeamType:number, - specialModes:number, - seed:number -} - -export interface MatchDataSlot { - status:number, - team:number, - playerId:number, - mods:number | undefined, -} - export class Match { // osu! Data public matchId:number = -1; @@ -58,12 +38,16 @@ export class Match { public matchStream:DataStream; public matchChatChannel:Channel; + public matchLoadSlots?:Array; + public matchSkippedSlots?:Array; + + public playerScores?:Array; + private cachedMatchJSON:MatchData; private readonly sharedContent:SharedContent; private constructor(matchData:MatchData, sharedContent:SharedContent) { this.sharedContent = sharedContent; - console.log(matchData); this.matchId = matchData.matchId; this.inProgress = matchData.inProgress; @@ -80,11 +64,12 @@ export class Match { this.beatmapId = matchData.beatmapId; this.beatmapChecksum = matchData.beatmapChecksum; - for (let slot of matchData.slots) { + for (let i = 0; i < matchData.slots.length; i++) { + const slot = matchData.slots[i]; if (slot.playerId === -1) { - this.slots.push(new Slot(slot.status, slot.team, undefined, slot.mods)); + this.slots.push(new Slot(i, slot.status, slot.team, undefined, slot.mods)); } else { - this.slots.push(new Slot(slot.status, slot.team, sharedContent.users.getById(slot.playerId), slot.mods)); + this.slots.push(new Slot(i, slot.status, slot.team, sharedContent.users.getById(slot.playerId), slot.mods)); } } @@ -104,14 +89,11 @@ export class Match { this.seed = matchData.seed; - this.matchStream = sharedContent.streams.CreateStream(`multiplayer:match_${this.matchId}`); + this.matchStream = sharedContent.streams.CreateStream(`multiplayer:match_${this.matchId}`, false); this.matchChatChannel = sharedContent.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`); this.cachedMatchJSON = matchData; - //this.matchLoadSlots = null; - //this.matchSkippedSlots = null; - //this.playerScores = null; //this.multiplayerExtras = null; @@ -130,8 +112,6 @@ export class Match { const matchInstance = new Match(matchData, sharedContent); - console.log(matchInstance.matchId); - // Update the status of the current user StatusUpdate(matchHost, matchHost.id); @@ -141,8 +121,7 @@ export class Match { matchHost.addActionToQueue(osuPacketWriter.toBuffer); - // Update the match listing for users in the multiplayer lobby - //global.MultiplayerManager.updateMatchListing(); + sharedContent.mutiplayerManager.UpdateLobbyListing(); resolve(matchInstance); } catch (e) { @@ -151,15 +130,8 @@ export class Match { }); } - /*getSlotIdByPlayerId(playerId = 0) { - const player = getUserById(playerId); - - if (player != null) return player.matchSlotId; - else return null; - }*/ - // Convert class data back to a format that osu-packet can understand - generateMatchJSON() : MatchData { + public generateMatchJSON() : MatchData { for (let i = 0; i < this.slots.length; i++) { const slot = this.slots[i]; const osuSlot = this.cachedMatchJSON.slots[i]; @@ -177,33 +149,27 @@ export class Match { return this.cachedMatchJSON; } - /*leaveMatch(MatchUser = new User) { + public leaveMatch(user:User) { // Make sure this leave call is valid - if (!MatchUser.inMatch) return; - - // Get the user's slot - const slot = this.slots[MatchUser.matchSlotId]; + if (!user.inMatch || user.matchSlot === undefined) { + return; + } // Set the slot's status to avaliable - slot.playerId = -1; - slot.status = 1; + user.matchSlot.status = SlotStatus.Empty; + user.matchSlot.team = 0; + user.matchSlot.player = undefined; + user.matchSlot.mods = 0; // Remove the leaving user from the match's stream - Streams.removeUserFromStream(this.matchStreamName, MatchUser.uuid); - Streams.removeUserFromStream(this.matchChatStreamName, MatchUser.uuid); + this.matchStream.RemoveUser(user); + this.matchChatChannel.Leave(user); // Send this after removing the user from match streams to avoid a leave notification for self this.sendMatchUpdate(); + } - const osuPacketWriter = new osu.Bancho.Writer; - - // Remove user from the multiplayer channel for the match - osuPacketWriter.ChannelRevoked("#multiplayer"); - - MatchUser.addActionToQueue(osuPacketWriter.toBuffer); - }*/ - - async updateMatch(user:User, matchData:MatchData) { + public async updateMatch(user:User, matchData:MatchData) { // Update match with new data this.inProgress = matchData.inProgress; @@ -228,21 +194,20 @@ export class Match { const hostUser = this.sharedContent.users.getById(matchData.host); if (hostUser === undefined) { // NOTE: This should never be possible to hit - // since this user JUST made the match. throw "Host User of match was undefined"; } this.host = hostUser; this.cachedMatchJSON.host = this.host.id; } - this.playMode = matchData.playMode; + this.playMode = this.cachedMatchJSON.playMode = matchData.playMode; - this.matchScoringType = matchData.matchScoringType; - this.matchTeamType = matchData.matchTeamType; - this.specialModes = matchData.specialModes; + this.matchScoringType = this.cachedMatchJSON.matchScoringType = matchData.matchScoringType; + this.matchTeamType = this.cachedMatchJSON.matchTeamType = matchData.matchTeamType; + this.specialModes = this.cachedMatchJSON.specialModes = matchData.specialModes; const gameSeedChanged = this.seed !== matchData.seed; - this.seed = matchData.seed; + this.seed = this.cachedMatchJSON.seed = matchData.seed; if (gameNameChanged || gameSeedChanged) { const queryData = []; @@ -260,7 +225,7 @@ export class Match { this.sendMatchUpdate(); // Update the match listing in the lobby to reflect these changes - //global.MultiplayerManager.updateMatchListing(); + this.sharedContent.mutiplayerManager.UpdateLobbyListing(); } public sendMatchUpdate() { @@ -272,126 +237,144 @@ export class Match { this.matchStream.Send(osuPacketWriter.toBuffer); } - /*moveToSlot(MatchUser = new User, SlotToMoveTo) { - const oldSlot = this.slots[MatchUser.matchSlotId]; + public moveToSlot(user:User, slotToMoveTo:number) { + if (slotToMoveTo < 0 || slotToMoveTo >= this.slots.length) { + return; + } - // Set the new slot's data to the user's old slot data - this.slots[SlotToMoveTo].playerId = MatchUser.id; - MatchUser.matchSlotId = SlotToMoveTo; - this.slots[SlotToMoveTo].status = 4; + const newSlot = this.slots[slotToMoveTo]; + if (newSlot.status === SlotStatus.Locked || !(user.matchSlot instanceof Slot)) { + return; + } - // Set the old slot's data to open - oldSlot.playerId = -1; - oldSlot.status = 1; + user.matchSlot = newSlot.transferFrom(user.matchSlot); this.sendMatchUpdate(); - // Update the match listing in the lobby to reflect this change - global.MultiplayerManager.updateMatchListing(); + this.sharedContent.mutiplayerManager.UpdateLobbyListing(); } - changeTeam(MatchUser = new User) { - const slot = this.slots[MatchUser.matchSlotId]; - slot.team = slot.team == 0 ? 1 : 0; + public changeTeam(user:User) { + if (!(user.matchSlot instanceof Slot)) { + return; + } + + user.matchSlot.team = user.matchSlot.team === Team.Red ? Team.Blue : Team.Red; this.sendMatchUpdate(); } - setStateReady(MatchUser = new User) { - if (!MatchUser.inMatch) return; - - // Set the user's ready state to ready - this.slots[MatchUser.matchSlotId].status = 8; + public setStateReady(user:User) { + if (!(user.matchSlot instanceof Slot)) { + return; + } + + user.matchSlot.status = SlotStatus.Ready; this.sendMatchUpdate(); } - setStateNotReady(MatchUser = new User) { - if (!MatchUser.inMatch) return; - - // Set the user's ready state to not ready - this.slots[MatchUser.matchSlotId].status = 4; + public setStateNotReady(user:User) { + if (!(user.matchSlot instanceof Slot)) { + return; + } + + user.matchSlot.status = SlotStatus.NotReady; this.sendMatchUpdate(); } - lockMatchSlot(MatchUser = new User, MatchUserToKick) { + public lockOrKick(user:User, slotToActionOn:number) { + if (slotToActionOn < 0 || slotToActionOn >= 16) { + return; + } + // Make sure the user attempting to kick / lock is the host of the match - if (this.host != MatchUser.id) return; - - // Make sure the user that is attempting to be kicked is not the host - if (this.slots[MatchUserToKick].playerId === this.host) return; - - // Get the data of the slot at the index sent by the client - const slot = this.slots[MatchUserToKick]; - - let isSlotEmpty = true; - - // If the slot is empty lock/unlock instead of kicking - if (slot.playerId === -1) - slot.status = slot.status === 1 ? 2 : 1; - - // The slot isn't empty, kick the player - else { - const kickedPlayer = getUserById(slot.playerId); - kickedPlayer.matchSlotId = -1; - slot.playerId = -1; - slot.status = 1; - isSlotEmpty = false; + if (User.Equals(user, this.host)) { + return; } - this.sendMatchUpdate(); - - // Update the match listing in the lobby listing to reflect this change - global.MultiplayerManager.updateMatchListing(); - - if (!isSlotEmpty) { - let cachedPlayerToken = getUserById(slot.playerId).uuid; - - if (cachedPlayerToken !== null && cachedPlayerToken !== "") { - // Remove the kicked user from the match stream - Streams.removeUserFromStream(this.matchStreamName, cachedPlayerToken); + const slot = this.slots[slotToActionOn]; + if (slot.player instanceof Slot) { + // Kick + if (User.Equals(user, slot.player)) { + return; } + + const kickedPlayer = slot.player; + + // Remove player's refs to the match & slot + kickedPlayer.match = undefined; + kickedPlayer.matchSlot = undefined; + + // Nuke all slot properties + slot.reset(); + + // Send update before removing the user from the stream so they know + // they got kicked + this.sendMatchUpdate(); + + // Remove player from stream and chat + this.matchStream.RemoveUser(kickedPlayer); + this.matchChatChannel.Leave(kickedPlayer); + } else { + // Lock / Unlock + slot.status = slot.status === SlotStatus.Empty ? SlotStatus.Locked : SlotStatus.Empty; + + this.sendMatchUpdate(); } + + this.sharedContent.mutiplayerManager.UpdateLobbyListing(); } - missingBeatmap(MatchUser = new User) { - // User is missing the beatmap set the status to reflect it - this.slots[MatchUser.matchSlotId].status = 16; + public missingBeatmap(user:User) { + const slot = user.matchSlot; + if (!(slot instanceof Slot)) { + return; + } + + slot.status = SlotStatus.MissingBeatmap; this.sendMatchUpdate(); } - notMissingBeatmap(MatchUser = new User) { - // The user is not missing the beatmap, set the status to normal - this.slots[MatchUser.matchSlotId].status = 4; + public notMissingBeatmap(user:User) { + const slot = user.matchSlot; + if (!(slot instanceof Slot)) { + return; + } + + slot.status = SlotStatus.NotReady; this.sendMatchUpdate(); } - matchSkip(MatchUser = new User) { - if (this.matchSkippedSlots == null) { - this.matchSkippedSlots = []; - - const skippedSlots = this.matchSkippedSlots; + public matchSkip(user:User) { + if (this.matchSkippedSlots === undefined) { + this.matchSkippedSlots = new Array(); for (let slot of this.slots) { // Make sure the slot has a user in it - if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) { + continue; + } // Add the slot's user to the loaded checking array - skippedSlots.push({playerId: slot.playerId, skipped: false}); + this.matchSkippedSlots.push({ + playerId: slot.player?.id, + flag: false + }); } } let allSkipped = true; for (let skippedSlot of this.matchSkippedSlots) { // If loadslot belongs to this user then set loaded to true - if (skippedSlot.playerId == MatchUser.id) { - skippedSlot.skipped = true; + if (skippedSlot.playerId === user.id) { + skippedSlot.flag = true; } - if (skippedSlot.skipped) continue; + if (skippedSlot.flag) continue; // A user hasn't skipped allSkipped = false; @@ -401,66 +384,77 @@ export class Match { if (allSkipped) { const osuPacketWriter = new osu.Bancho.Writer; - osuPacketWriter.MatchPlayerSkipped(MatchUser.id); + osuPacketWriter.MatchPlayerSkipped(user.id); osuPacketWriter.MatchSkip(); - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); + this.matchStream.Send(osuPacketWriter.toBuffer); - this.matchSkippedSlots = null; + this.matchSkippedSlots = undefined; } else { const osuPacketWriter = new osu.Bancho.Writer; - osuPacketWriter.MatchPlayerSkipped(MatchUser.id); + osuPacketWriter.MatchPlayerSkipped(user.id); - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); + this.matchStream.Send(osuPacketWriter.toBuffer); } } - transferHost(MatchUser = new User, SlotIDToTransferTo) { + public transferHost(user:User, slotIDToTransferTo:number) { // Set the lobby's host to the new user - this.host = this.slots[SlotIDToTransferTo].playerId; + const newHost = this.slots[slotIDToTransferTo].player; + if (newHost instanceof Slot) { + this.host = newHost; + this.cachedMatchJSON.host = this.host.id; - this.sendMatchUpdate(); + this.sendMatchUpdate(); + } } // TODO: Fix not being able to add DT when freemod is active - updateMods(MatchUser = new User, MatchMods) { - // Check if freemod is enabled + public updateMods(user:User, mods:Mods) { + const slot = user.matchSlot; + if (!(slot instanceof Slot)) { + return; + } + + // Check if freemod is enabled or not if (this.specialModes === 1) { - this.slots[MatchUser.matchSlotId].mods = MatchMods; + slot.mods = mods; this.sendMatchUpdate(); } else { - // Make sure the person updating mods is the host of the match - if (this.host !== MatchUser.id) return; + if (!User.Equals(this.host, user)) { + return; + } - // Change the matches mods to these new mods - // TODO: Do this per user if freemod is enabled - this.activeMods = MatchMods; + this.activeMods = mods; this.sendMatchUpdate(); } - // Update match listing in the lobby to reflect this change - global.MultiplayerManager.updateMatchListing(); + this.sharedContent.mutiplayerManager.UpdateLobbyListing(); } startMatch() { // Make sure the match is not already in progress // The client sometimes double fires the start packet - if (this.inProgress) return; + if (this.inProgress) { + return; + } this.inProgress = true; - // Create array for monitoring users until they are ready to play - this.matchLoadSlots = []; + + this.matchLoadSlots = new Array(); // Loop through all slots in the match for (let slot of this.slots) { // Make sure the slot has a user in it - if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) { + continue; + } // Add the slot's user to the loaded checking array this.matchLoadSlots.push({ - playerId: slot.playerId, - loaded: false + playerId: slot.player?.id, + flag: false }); // Set the user's status to playing @@ -469,28 +463,30 @@ export class Match { const osuPacketWriter = new osu.Bancho.Writer; - osuPacketWriter.MatchStart(this.createOsuMatchJSON()); + osuPacketWriter.MatchStart(this.generateMatchJSON()); // Inform all users in the match that it has started - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); + this.matchStream.Send(osuPacketWriter.toBuffer); // Update all users in the match with new info this.sendMatchUpdate(); // Update match listing in lobby to show the game is in progress - global.MultiplayerManager.updateMatchListing(); + this.sharedContent.mutiplayerManager.UpdateLobbyListing(); } - matchPlayerLoaded(MatchUser = new User) { - // Loop through all user load check items and check if all users are loaded + public matchPlayerLoaded(user:User) { + if (this.matchLoadSlots === undefined) { + return; + } + let allLoaded = true; for (let loadedSlot of this.matchLoadSlots) { - // If loadslot belongs to this user then set loaded to true - if (loadedSlot.playerId == MatchUser.id) { - loadedSlot.loaded = true; + if (loadedSlot.playerId === user.id) { + loadedSlot.flag = true; } - if (loadedSlot.loaded) continue; + if (loadedSlot.flag) continue; allLoaded = false; } @@ -499,46 +495,54 @@ export class Match { if (allLoaded) { let osuPacketWriter = new osu.Bancho.Writer; osuPacketWriter.MatchAllPlayersLoaded(); - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); + this.matchStream.Send(osuPacketWriter.toBuffer); // Blank out user loading array - this.matchLoadSlots = null; + this.matchLoadSlots = undefined; - this.playerScores = []; - for (let i = 0; i < this.slots.length; i++) { - const slot = this.slots[i]; - if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + this.playerScores = new Array(); + for (let slot of this.slots) { + if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) { + continue; + } - this.playerScores.push({playerId: slot.playerId, slotId: i, score: 0, isCurrentlyFailed: false}); + this.playerScores.push({ + player: slot.player, + slot: slot, + score: 0, + isCurrentlyFailed: false, + hasFailed: false, + _raw: {} + }); } } } - async onPlayerFinishMatch(MatchUser = new User) { - if (this.matchLoadSlots == null) { + public async onPlayerFinishMatch(user:User) { + if (this.matchLoadSlots === undefined) { // Repopulate user loading slots again this.matchLoadSlots = []; for (let slot of this.slots) { // Make sure the slot has a user - if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) { + continue; + } // Populate user loading slots with this user's id and load status this.matchLoadSlots.push({ - playerId: slot.playerId, - loaded: false + playerId: slot.player?.id, + flag: false }); } } let allLoaded = true; - - // Loop through all loaded slots to make sure all users have finished playing for (let loadedSlot of this.matchLoadSlots) { - if (loadedSlot.playerId == MatchUser.id) { - loadedSlot.loaded = true; + if (loadedSlot.playerId == user.id) { + loadedSlot.flag = true; } - if (loadedSlot.loaded) continue; + if (loadedSlot.flag) continue; // A user hasn't finished playing allLoaded = false; @@ -548,89 +552,108 @@ export class Match { if (allLoaded) await this.finishMatch(); } - async finishMatch() { - if (!this.inProgress) return; - this.matchLoadSlots = null; + public finishMatch() { + if (!this.inProgress) { + return; + } + + this.matchLoadSlots = undefined; this.inProgress = false; + let osuPacketWriter = new osu.Bancho.Writer; - let queryData = [this.matchId, this.roundId++, this.playMode, this.matchType, this.matchScoringType, this.matchTeamType, this.activeMods, this.beatmapChecksum, (this.specialModes === 1) ? 1 : 0]; + let queryData:Array = [ + this.matchId, + this.roundId++, + this.playMode, + this.matchType, + this.matchScoringType, + this.matchTeamType, + this.activeMods, + this.beatmapChecksum, + (this.specialModes === 1) ? 1 : 0 + ]; + + if (this.playerScores === undefined) { + throw "playerScores was null in a place it really shouldn't have been!"; + } - // Loop through all slots in the match for (let slot of this.slots) { - // Make sure the slot has a user - if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) { + // For every empty / locked slot push a null to the data array + if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) { queryData.push(null); continue; } - let score = null; for (let _playerScore of this.playerScores) { - if (_playerScore.playerId === slot.playerId) { - score = _playerScore._raw; + if (_playerScore.player?.id === slot.player?.id) { + const score = _playerScore._raw; + queryData.push(`${slot.player?.id}|${score.totalScore}|${score.maxCombo}|${score.count300}|${score.count100}|${score.count50}|${score.countGeki}|${score.countKatu}|${score.countMiss}|${(score.currentHp == 254) ? 1 : 0}${(this.specialModes === 1) ? `|${slot.mods}` : ""}|${score.usingScoreV2 ? 1 : 0}${score.usingScoreV2 ? `|${score.comboPortion}|${score.bonusPortion}` : ""}`); break; } } - queryData.push(`${slot.playerId}|${score.totalScore}|${score.maxCombo}|${score.count300}|${score.count100}|${score.count50}|${score.countGeki}|${score.countKatu}|${score.countMiss}|${(score.currentHp == 254) ? 1 : 0}${(this.specialModes === 1) ? `|${slot.mods}` : ""}|${score.usingScoreV2 ? 1 : 0}${score.usingScoreV2 ? `|${score.comboPortion}|${score.bonusPortion}` : ""}`); - - // Set the user's status back to normal from playing - slot.status = 4; + slot.status = SlotStatus.NotReady; } - console.log(queryData); - osuPacketWriter.MatchComplete(); // Inform all users in the match that it is complete - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); - + this.matchStream.Send(osuPacketWriter.toBuffer); // Update all users in the match with new info this.sendMatchUpdate(); - // Update match info in the lobby to reflect that the match has finished - global.MultiplayerManager.updateMatchListing(); + this.sharedContent.mutiplayerManager.UpdateLobbyListing(); - if (this.multiplayerExtras != null) this.multiplayerExtras.onMatchFinished(JSON.parse(JSON.stringify(this.playerScores))); + // TODO: Re-implement multiplayer extras + //if (this.multiplayerExtras != null) this.multiplayerExtras.onMatchFinished(JSON.parse(JSON.stringify(this.playerScores))); - await global.DatabaseHelper.query("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData); + this.sharedContent.database.query("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData); - this.playerScores = null; + this.playerScores = undefined; } - updatePlayerScore(MatchPlayer = new User, MatchScoreData) { + // TODO: Interface type for matchScoreData + updatePlayerScore(user:User, matchScoreData:any) { const osuPacketWriter = new osu.Bancho.Writer; - // Make sure the user's slot ID is not invalid - if (this.matchSlotId == -1) return; + if (user.matchSlot === undefined || this.playerScores === undefined) { + return; + } - // Get the user's current slotID and append it to the givien data, just incase. - MatchScoreData.id = MatchPlayer.matchSlotId; + matchScoreData.id = user.matchSlot; - // Update the playerScores array accordingly + // Update playerScores for (let playerScore of this.playerScores) { - if (playerScore.playerId == MatchPlayer.id) { - playerScore.score = MatchScoreData.totalScore; - playerScore.isCurrentlyFailed = MatchScoreData.currentHp == 254; - playerScore._raw = MatchScoreData; + if (playerScore.player?.id == user.id) { + playerScore.score = matchScoreData.totalScore; + const isCurrentlyFailed = matchScoreData.currentHp == 254; + playerScore.isCurrentlyFailed = isCurrentlyFailed; + if (!playerScore.hasFailed && isCurrentlyFailed) { + playerScore.hasFailed = true; + } + playerScore._raw = matchScoreData; + break; } } - osuPacketWriter.MatchScoreUpdate(MatchScoreData); + osuPacketWriter.MatchScoreUpdate(matchScoreData); // Send the newly updated score to all users in the match - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); + this.matchStream.Send(osuPacketWriter.toBuffer); } - matchFailed(MatchUser = new User) { + matchFailed(user:User) { const osuPacketWriter = new osu.Bancho.Writer; - // Make sure the user's slot ID is not invalid - if (MatchUser.matchSlotId == -1) return; + // Make sure the user is in the match in a valid slot + if (user.matchSlot === undefined) { + return; + } - osuPacketWriter.MatchPlayerFailed(MatchUser.id); + osuPacketWriter.MatchPlayerFailed(user.id); - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); - }*/ -} \ No newline at end of file + this.matchStream.Send(osuPacketWriter.toBuffer); + } +} diff --git a/server/objects/MatchArray.ts b/server/objects/MatchArray.ts new file mode 100644 index 0000000..a5628ea --- /dev/null +++ b/server/objects/MatchArray.ts @@ -0,0 +1,14 @@ +import { FunkyArray } from "./FunkyArray"; +import { Match } from "./Match"; + +export class MatchArray extends FunkyArray { + public getById(id:number) : Match | undefined { + for (let match of this.getIterableItems()) { + if (match.matchId == id) { + return match; + } + } + + return undefined; + } +} \ No newline at end of file diff --git a/server/objects/Slot.ts b/server/objects/Slot.ts index a926138..8dc2ed5 100644 --- a/server/objects/Slot.ts +++ b/server/objects/Slot.ts @@ -1,16 +1,48 @@ +import { Mods } from "../enums/Mods"; import { SlotStatus } from "../enums/SlotStatus"; import { User } from "./User"; export class Slot { + public readonly slotId:number; public status:SlotStatus; public team:number; - public player?:User; // playerId - public mods:number; + public player?:User; + public mods:Mods; - public constructor(status:SlotStatus, team:number, player?:User, mods:number = 0) { + public constructor(slotId:number, status:SlotStatus, team:number, player?:User, mods:Mods = Mods.None) { + this.slotId = slotId; this.status = status; this.team = team; this.player = player; this.mods = mods; } + + public transferFrom(slot:Slot) : Slot { + this.status = slot.status; + this.team = slot.team; + this.player = slot.player; + this.mods = slot.mods; + slot.reset(); + + return this; + } + + public transferTo(slot:Slot) : Slot { + slot.status = this.status; + slot.team = this.team; + slot.player = this.player; + slot.mods = this.mods; + this.reset(); + + return slot; + } + + public reset() : Slot { + this.status = SlotStatus.Empty; + this.team = 0; + this.player = undefined; + this.mods = Mods.None; + + return this; + } } \ No newline at end of file diff --git a/server/objects/User.ts b/server/objects/User.ts index 73f63b0..1741aa7 100644 --- a/server/objects/User.ts +++ b/server/objects/User.ts @@ -3,7 +3,8 @@ import { RankingModes } from "../enums/RankingModes"; import { Match } from "./Match"; import { DataStream } from "./DataStream"; import { StatusUpdate } from "../packets/StatusUpdate"; -import { SharedContent } from "../BanchoServer"; +import { SharedContent } from "../interfaces/SharedContent"; +import { Slot } from "./Slot"; const rankingModes = [ "pp_raw", @@ -52,13 +53,19 @@ export class User { public pp:number = 0; // Multiplayer data - public currentMatch:Match | null = null; - public matchSlotId:number = -1; - public inMatch:boolean = false; + public match?:Match; + public matchSlot?:Slot; + public get inMatch() { + return this.match instanceof Match; + } // Tournament client flag public isTourneyUser:boolean = false; + static Equals(user0:User, user1:User) { + return user0.uuid === user1.uuid; + } + public constructor(id:number, username:string, uuid:string, sharedContent:SharedContent) { this.id = id; this.username = username; diff --git a/server/packets/StatusUpdate.ts b/server/packets/StatusUpdate.ts index d7bf500..6c50624 100644 --- a/server/packets/StatusUpdate.ts +++ b/server/packets/StatusUpdate.ts @@ -1,4 +1,4 @@ -import { SharedContent } from "../BanchoServer"; +import { SharedContent } from "../interfaces/SharedContent"; import { RankingModes } from "../enums/RankingModes"; import { User } from "../objects/User"; const osu = require("osu-packet"); diff --git a/server/packets/UserPresence.ts b/server/packets/UserPresence.ts index 21f8c4b..e440176 100644 --- a/server/packets/UserPresence.ts +++ b/server/packets/UserPresence.ts @@ -1,4 +1,4 @@ -import { SharedContent } from "../BanchoServer"; +import { SharedContent } from "../interfaces/SharedContent"; import { User } from "../objects/User"; const osu = require("osu-packet"); diff --git a/server/packets/UserPresenceBundle.ts b/server/packets/UserPresenceBundle.ts index bca8716..f170bd9 100644 --- a/server/packets/UserPresenceBundle.ts +++ b/server/packets/UserPresenceBundle.ts @@ -1,4 +1,4 @@ -import { SharedContent } from "../BanchoServer"; +import { SharedContent } from "../interfaces/SharedContent"; import { User } from "../objects/User"; const osu = require("osu-packet");