diff --git a/server/MultiplayerManager.ts b/server/MultiplayerManager.ts index d95aef4..19a6c14 100644 --- a/server/MultiplayerManager.ts +++ b/server/MultiplayerManager.ts @@ -1,9 +1,14 @@ import { SharedContent } from "./BanchoServer"; +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 { User } from "./objects/User"; +import { StatusUpdate } from "./packets/StatusUpdate"; +import { UserPresence } from "./packets/UserPresence"; +import { UserPresenceBundle } from "./packets/UserPresenceBundle"; +const osu = require("osu-packet"); export class MultiplayerManager { private readonly sharedContent:SharedContent; @@ -21,6 +26,38 @@ export class MultiplayerManager { } } + public UpdateLobbyListing() { + this.lobbyStream.Send(this.GenerateLobbyListing()); + } + + public GenerateLobbyListing(user?:User) : Buffer { + const osuPacketWriter = new osu.Bancho.Writer; + let bufferToSend = UserPresenceBundle(this.sharedContent); + + for (let match of this.matches.getIterableItems()) { + for (let slot of match.slots) { + if (!(slot.player instanceof User) || slot.status === SlotStatus.Locked) { + continue; + } + + const presenceBuffer = UserPresence(this.sharedContent, slot.player.id); + const statusBuffer = StatusUpdate(this.sharedContent, slot.player.id); + bufferToSend = Buffer.concat([bufferToSend, presenceBuffer, statusBuffer], bufferToSend.length + presenceBuffer.length + statusBuffer.length); + } + + osuPacketWriter.MatchNew(match.generateMatchJSON()); + } + + const osuBuffer = osuPacketWriter.toBuffer; + bufferToSend = Buffer.concat([bufferToSend, osuBuffer], bufferToSend.length + osuBuffer.length); + + if (user instanceof User) { + user.addActionToQueue(bufferToSend); + } + + return bufferToSend; + } + public async CreateMatch(user:User, matchData:MatchData) { const match = await Match.createMatch(user, matchData, this.sharedContent); this.matches.add(match.matchId.toString(), match); diff --git a/server/enums/Mods.ts b/server/enums/Mods.ts index b96df8e..8923447 100644 --- a/server/enums/Mods.ts +++ b/server/enums/Mods.ts @@ -1,3 +1,4 @@ +// TODO: Mods enum export enum Mods { } \ No newline at end of file diff --git a/server/enums/SlotStatus.ts b/server/enums/SlotStatus.ts new file mode 100644 index 0000000..8a8cdb2 --- /dev/null +++ b/server/enums/SlotStatus.ts @@ -0,0 +1,6 @@ +export enum SlotStatus { + Unknown_0, + Empty, + Locked, + NotReady, +} \ No newline at end of file diff --git a/server/objects/Match.ts b/server/objects/Match.ts index 61f9eb7..49a9303 100644 --- a/server/objects/Match.ts +++ b/server/objects/Match.ts @@ -17,7 +17,7 @@ export interface MatchData { beatmapName:string, beatmapId:number, beatmapChecksum:string, - slots:Array, + slots:Array, host:number, playMode:number, matchScoringType:number, @@ -26,6 +26,13 @@ export interface MatchData { seed:number } +export interface MatchDataSlot { + status:number, + team:number, + playerId:number, + mods:number | undefined, +} + export class Match { // osu! Data public matchId:number = -1; @@ -33,12 +40,12 @@ export class Match { public matchType:number = 0; public activeMods:number = 0; public gameName:string = ""; - public gamePassword:string | undefined = ''; + public gamePassword?:string; public beatmapName:string = ''; public beatmapId:number = 0; public beatmapChecksum:string = ''; public slots:Array = new Array(); - public host:number = 0; + public host:User; public playMode:number = 0; public matchScoringType:number = 0; public matchTeamType:number = 0; @@ -51,7 +58,11 @@ export class Match { public matchStream:DataStream; public matchChatChannel:Channel; + private cachedMatchJSON:MatchData; + private readonly sharedContent:SharedContent; + private constructor(matchData:MatchData, sharedContent:SharedContent) { + this.sharedContent = sharedContent; console.log(matchData); this.matchId = matchData.matchId; @@ -69,12 +80,21 @@ export class Match { this.beatmapId = matchData.beatmapId; this.beatmapChecksum = matchData.beatmapChecksum; - this.slots = matchData.slots; - for (let i = 0; i < this.slots.length; i++) { - //this.slots[i].mods = 0; + for (let slot of matchData.slots) { + if (slot.playerId === -1) { + this.slots.push(new Slot(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.host = matchData.host; + const hostUser = 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.playMode = matchData.playMode; @@ -87,6 +107,8 @@ export class Match { this.matchStream = sharedContent.streams.CreateStream(`multiplayer:match_${this.matchId}`); this.matchChatChannel = sharedContent.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`); + this.cachedMatchJSON = matchData; + //this.matchLoadSlots = null; //this.matchSkippedSlots = null; @@ -115,7 +137,7 @@ export class Match { const osuPacketWriter = new osu.Bancho.Writer; - //osuPacketWriter.MatchNew(matchInstance.createOsuMatchJSON()); + osuPacketWriter.MatchNew(matchInstance.generateMatchJSON()); matchHost.addActionToQueue(osuPacketWriter.toBuffer); @@ -134,30 +156,28 @@ export class Match { if (player != null) return player.matchSlotId; else return null; + }*/ + + // Convert class data back to a format that osu-packet can understand + generateMatchJSON() : MatchData { + for (let i = 0; i < this.slots.length; i++) { + const slot = this.slots[i]; + const osuSlot = this.cachedMatchJSON.slots[i]; + + osuSlot.status = slot.status; + osuSlot.team = slot.team; + osuSlot.mods = slot.mods; + if (slot.player instanceof User) { + osuSlot.playerId = slot.player.id; + } else { + osuSlot.playerId = -1; + } + } + + return this.cachedMatchJSON; } - createOsuMatchJSON() { - return { - matchId: this.matchId, - inProgress: this.inProgress, - matchType: this.matchType, - activeMods: this.activeMods, - gameName: this.gameName, - gamePassword: this.gamePassword, - beatmapName: this.beatmapName, - beatmapId: this.beatmapId, - beatmapChecksum: this.beatmapChecksum, - slots: this.slots, - host: this.host, - playMode: this.playMode, - matchScoringType: this.matchScoringType, - matchTeamType: this.matchTeamType, - specialModes: this.specialModes, - seed: this.seed - }; - } - - leaveMatch(MatchUser = new User) { + /*leaveMatch(MatchUser = new User) { // Make sure this leave call is valid if (!MatchUser.inMatch) return; @@ -181,67 +201,78 @@ export class Match { osuPacketWriter.ChannelRevoked("#multiplayer"); MatchUser.addActionToQueue(osuPacketWriter.toBuffer); - } + }*/ - async updateMatch(MatchUser = new User, MatchData) { + async updateMatch(user:User, matchData:MatchData) { // Update match with new data - this.inProgress = MatchData.inProgress; + this.inProgress = matchData.inProgress; - this.matchType = MatchData.matchType; + this.matchType = matchData.matchType; - this.activeMods = MatchData.activeMods; + this.activeMods = matchData.activeMods; - const gameNameChanged = this.gameName !== MatchData.gameName; - this.gameName = MatchData.gameName; + const gameNameChanged = this.gameName !== matchData.gameName; + this.gameName = matchData.gameName; - if (MatchData.gamePassword == '') MatchData.gamePassword == null; - this.gamePassword = MatchData.gamePassword; + if (matchData.gamePassword === "") { + this.gamePassword = undefined; + } else { + this.gamePassword = this.cachedMatchJSON.gamePassword = matchData.gamePassword; + } - this.beatmapName = MatchData.beatmapName; - this.beatmapId = MatchData.beatmapId; - this.beatmapChecksum = MatchData.beatmapChecksum; + this.beatmapName = this.cachedMatchJSON.beatmapName = matchData.beatmapName; + this.beatmapId = this.cachedMatchJSON.beatmapId = matchData.beatmapId; + this.beatmapChecksum = this.cachedMatchJSON.beatmapChecksum = matchData.beatmapChecksum; - this.host = MatchData.host; + if (matchData.host !== this.host.id) { + 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 = matchData.playMode; - this.matchScoringType = MatchData.matchScoringType; - this.matchTeamType = MatchData.matchTeamType; - this.specialModes = MatchData.specialModes; + this.matchScoringType = matchData.matchScoringType; + this.matchTeamType = matchData.matchTeamType; + this.specialModes = matchData.specialModes; - const gameSeedChanged = this.seed !== MatchData.seed; - this.seed = MatchData.seed; + const gameSeedChanged = this.seed !== matchData.seed; + this.seed = matchData.seed; if (gameNameChanged || gameSeedChanged) { const queryData = []; if (gameNameChanged) { - queryData.push(MatchData.gameName); + queryData.push(matchData.gameName); } if (gameSeedChanged) { - queryData.push(MatchData.seed); + queryData.push(matchData.seed); } queryData.push(this.matchId); - await global.DatabaseHelper.query(`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, queryData); + await this.sharedContent.database.query(`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, queryData); } this.sendMatchUpdate(); // Update the match listing in the lobby to reflect these changes - global.MultiplayerManager.updateMatchListing(); + //global.MultiplayerManager.updateMatchListing(); } - sendMatchUpdate() { + public sendMatchUpdate() { const osuPacketWriter = new osu.Bancho.Writer; - osuPacketWriter.MatchUpdate(this.createOsuMatchJSON()); + osuPacketWriter.MatchUpdate(this.generateMatchJSON()); // Update all users in the match with new match information - if (Streams.exists(this.matchStreamName)) - Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null); + this.matchStream.Send(osuPacketWriter.toBuffer); } - moveToSlot(MatchUser = new User, SlotToMoveTo) { + /*moveToSlot(MatchUser = new User, SlotToMoveTo) { const oldSlot = this.slots[MatchUser.matchSlotId]; // Set the new slot's data to the user's old slot data diff --git a/server/objects/Slot.ts b/server/objects/Slot.ts index 39b5405..a926138 100644 --- a/server/objects/Slot.ts +++ b/server/objects/Slot.ts @@ -1,13 +1,16 @@ +import { SlotStatus } from "../enums/SlotStatus"; +import { User } from "./User"; + export class Slot { - public status:number; + public status:SlotStatus; public team:number; - public player:number; // playerId + public player?:User; // playerId public mods:number; - public constructor() { - this.status = 0; - this.team = 0; - this.player = 0; - this.mods = 0; + public constructor(status:SlotStatus, team:number, player?:User, mods:number = 0) { + this.status = status; + this.team = team; + this.player = player; + this.mods = mods; } } \ No newline at end of file diff --git a/server/packets/ChangeAction.ts b/server/packets/ChangeAction.ts index 137f06f..6903833 100644 --- a/server/packets/ChangeAction.ts +++ b/server/packets/ChangeAction.ts @@ -5,7 +5,7 @@ export function ChangeAction(user:User, data:any) { user.updatePresence(data); if (user.spectatorStream != null) { - const statusUpdate = StatusUpdate(user, user.id, false); + const statusUpdate = StatusUpdate(user.sharedContent, user.id); user.spectatorStream.Send(statusUpdate); } } \ No newline at end of file diff --git a/server/packets/StatusUpdate.ts b/server/packets/StatusUpdate.ts index f6ba123..d7bf500 100644 --- a/server/packets/StatusUpdate.ts +++ b/server/packets/StatusUpdate.ts @@ -1,15 +1,22 @@ +import { SharedContent } from "../BanchoServer"; import { RankingModes } from "../enums/RankingModes"; import { User } from "../objects/User"; const osu = require("osu-packet"); -export function StatusUpdate(user:User, id:number, sendImmidiate:boolean = true) { +export function StatusUpdate(arg0:User | SharedContent, id:number) { if (id == 3) return; // Ignore Bot // Create new osu packet writer const osuPacketWriter = new osu.Bancho.Writer; + let sharedContent:SharedContent; + if (arg0 instanceof User) { + sharedContent = arg0.sharedContent; + } else { + sharedContent = arg0; + } // Get user's class - const userData = user.sharedContent.users.getById(id); + const userData = sharedContent.users.getById(id); if (userData == null) return; @@ -32,6 +39,9 @@ export function StatusUpdate(user:User, id:number, sendImmidiate:boolean = true) osuPacketWriter.HandleOsuUpdate(UserStatusObject); // Send data to user's queue - if (sendImmidiate) user.addActionToQueue(osuPacketWriter.toBuffer); - else return osuPacketWriter.toBuffer; + if (arg0 instanceof User) { + arg0.addActionToQueue(osuPacketWriter.toBuffer); + } + + return osuPacketWriter.toBuffer; } \ No newline at end of file diff --git a/server/packets/UserPresence.ts b/server/packets/UserPresence.ts index 83258b0..21f8c4b 100644 --- a/server/packets/UserPresence.ts +++ b/server/packets/UserPresence.ts @@ -1,10 +1,17 @@ +import { SharedContent } from "../BanchoServer"; import { User } from "../objects/User"; const osu = require("osu-packet"); -export function UserPresence(user:User, id:number, sendImmidiate:boolean = true) { +export function UserPresence(arg0:User | SharedContent, id:number) { const osuPacketWriter = new osu.Bancho.Writer; + let sharedContent:SharedContent; + if (arg0 instanceof User) { + sharedContent = arg0.sharedContent; + } else { + sharedContent = arg0; + } - const userData = user.sharedContent.users.getById(id); + const userData = sharedContent.users.getById(id); if (userData == null) return; @@ -19,6 +26,9 @@ export function UserPresence(user:User, id:number, sendImmidiate:boolean = true) rank: userData.rank }); - if (sendImmidiate) userData.addActionToQueue(osuPacketWriter.toBuffer); - else return osuPacketWriter.toBuffer; + if (arg0 instanceof User) { + arg0.addActionToQueue(osuPacketWriter.toBuffer); + } + + return osuPacketWriter.toBuffer; } \ No newline at end of file diff --git a/server/packets/UserPresenceBundle.ts b/server/packets/UserPresenceBundle.ts index 30d0ef0..bca8716 100644 --- a/server/packets/UserPresenceBundle.ts +++ b/server/packets/UserPresenceBundle.ts @@ -1,17 +1,27 @@ +import { SharedContent } from "../BanchoServer"; import { User } from "../objects/User"; const osu = require("osu-packet"); -export function UserPresenceBundle(user:User, sendImmidiate:boolean = true) { +export function UserPresenceBundle(arg0:User | SharedContent) : Buffer { const osuPacketWriter = new osu.Bancho.Writer; + let sharedContent:SharedContent; + if (arg0 instanceof User) { + sharedContent = arg0.sharedContent; + } else { + sharedContent = arg0; + } let userIds:Array = new Array(); - for (let userData of user.sharedContent.users.getIterableItems()) { + for (let userData of sharedContent.users.getIterableItems()) { userIds.push(userData.id); } osuPacketWriter.UserPresenceBundle(userIds); - if (sendImmidiate) user.addActionToQueue(osuPacketWriter.toBuffer); - else return osuPacketWriter.toBuffer; + if (arg0 instanceof User) { + arg0.addActionToQueue(osuPacketWriter.toBuffer); + } + + return osuPacketWriter.toBuffer; } \ No newline at end of file