more multiplayer stuff

This commit is contained in:
Holly Stubbs 2022-11-21 23:26:20 +00:00
parent e297aa1128
commit be52b19002
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
9 changed files with 186 additions and 78 deletions

View file

@ -1,9 +1,14 @@
import { SharedContent } from "./BanchoServer"; import { SharedContent } from "./BanchoServer";
import { SlotStatus } from "./enums/SlotStatus";
import { DataStream } from "./objects/DataStream"; import { DataStream } from "./objects/DataStream";
import { DataStreamArray } from "./objects/DataStreamArray"; import { DataStreamArray } from "./objects/DataStreamArray";
import { FunkyArray } from "./objects/FunkyArray"; import { FunkyArray } from "./objects/FunkyArray";
import { Match, MatchData } from "./objects/Match"; import { Match, MatchData } from "./objects/Match";
import { User } from "./objects/User"; 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 { export class MultiplayerManager {
private readonly sharedContent:SharedContent; 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) { public async CreateMatch(user:User, matchData:MatchData) {
const match = await Match.createMatch(user, matchData, this.sharedContent); const match = await Match.createMatch(user, matchData, this.sharedContent);
this.matches.add(match.matchId.toString(), match); this.matches.add(match.matchId.toString(), match);

View file

@ -1,3 +1,4 @@
// TODO: Mods enum
export enum Mods { export enum Mods {
} }

View file

@ -0,0 +1,6 @@
export enum SlotStatus {
Unknown_0,
Empty,
Locked,
NotReady,
}

View file

@ -17,7 +17,7 @@ export interface MatchData {
beatmapName:string, beatmapName:string,
beatmapId:number, beatmapId:number,
beatmapChecksum:string, beatmapChecksum:string,
slots:Array<any>, slots:Array<MatchDataSlot>,
host:number, host:number,
playMode:number, playMode:number,
matchScoringType:number, matchScoringType:number,
@ -26,6 +26,13 @@ export interface MatchData {
seed:number seed:number
} }
export interface MatchDataSlot {
status:number,
team:number,
playerId:number,
mods:number | undefined,
}
export class Match { export class Match {
// osu! Data // osu! Data
public matchId:number = -1; public matchId:number = -1;
@ -33,12 +40,12 @@ export class Match {
public matchType:number = 0; public matchType:number = 0;
public activeMods:number = 0; public activeMods:number = 0;
public gameName:string = ""; public gameName:string = "";
public gamePassword:string | undefined = ''; public gamePassword?:string;
public beatmapName:string = ''; public beatmapName:string = '';
public beatmapId:number = 0; public beatmapId:number = 0;
public beatmapChecksum:string = ''; public beatmapChecksum:string = '';
public slots:Array<Slot> = new Array<Slot>(); public slots:Array<Slot> = new Array<Slot>();
public host:number = 0; public host:User;
public playMode:number = 0; public playMode:number = 0;
public matchScoringType:number = 0; public matchScoringType:number = 0;
public matchTeamType:number = 0; public matchTeamType:number = 0;
@ -51,7 +58,11 @@ export class Match {
public matchStream:DataStream; public matchStream:DataStream;
public matchChatChannel:Channel; public matchChatChannel:Channel;
private cachedMatchJSON:MatchData;
private readonly sharedContent:SharedContent;
private constructor(matchData:MatchData, sharedContent:SharedContent) { private constructor(matchData:MatchData, sharedContent:SharedContent) {
this.sharedContent = sharedContent;
console.log(matchData); console.log(matchData);
this.matchId = matchData.matchId; this.matchId = matchData.matchId;
@ -69,12 +80,21 @@ export class Match {
this.beatmapId = matchData.beatmapId; this.beatmapId = matchData.beatmapId;
this.beatmapChecksum = matchData.beatmapChecksum; this.beatmapChecksum = matchData.beatmapChecksum;
this.slots = matchData.slots; for (let slot of matchData.slots) {
for (let i = 0; i < this.slots.length; i++) { if (slot.playerId === -1) {
//this.slots[i].mods = 0; 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; this.playMode = matchData.playMode;
@ -87,6 +107,8 @@ export class Match {
this.matchStream = sharedContent.streams.CreateStream(`multiplayer:match_${this.matchId}`); this.matchStream = sharedContent.streams.CreateStream(`multiplayer:match_${this.matchId}`);
this.matchChatChannel = sharedContent.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`); this.matchChatChannel = sharedContent.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`);
this.cachedMatchJSON = matchData;
//this.matchLoadSlots = null; //this.matchLoadSlots = null;
//this.matchSkippedSlots = null; //this.matchSkippedSlots = null;
@ -115,7 +137,7 @@ export class Match {
const osuPacketWriter = new osu.Bancho.Writer; const osuPacketWriter = new osu.Bancho.Writer;
//osuPacketWriter.MatchNew(matchInstance.createOsuMatchJSON()); osuPacketWriter.MatchNew(matchInstance.generateMatchJSON());
matchHost.addActionToQueue(osuPacketWriter.toBuffer); matchHost.addActionToQueue(osuPacketWriter.toBuffer);
@ -134,30 +156,28 @@ export class Match {
if (player != null) return player.matchSlotId; if (player != null) return player.matchSlotId;
else return null; 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;
}
} }
createOsuMatchJSON() { return this.cachedMatchJSON;
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 // Make sure this leave call is valid
if (!MatchUser.inMatch) return; if (!MatchUser.inMatch) return;
@ -181,67 +201,78 @@ export class Match {
osuPacketWriter.ChannelRevoked("#multiplayer"); osuPacketWriter.ChannelRevoked("#multiplayer");
MatchUser.addActionToQueue(osuPacketWriter.toBuffer); MatchUser.addActionToQueue(osuPacketWriter.toBuffer);
}*/
async updateMatch(user:User, matchData:MatchData) {
// Update match with new data
this.inProgress = matchData.inProgress;
this.matchType = matchData.matchType;
this.activeMods = matchData.activeMods;
const gameNameChanged = this.gameName !== matchData.gameName;
this.gameName = matchData.gameName;
if (matchData.gamePassword === "") {
this.gamePassword = undefined;
} else {
this.gamePassword = this.cachedMatchJSON.gamePassword = matchData.gamePassword;
} }
async updateMatch(MatchUser = new User, MatchData) { this.beatmapName = this.cachedMatchJSON.beatmapName = matchData.beatmapName;
// Update match with new data this.beatmapId = this.cachedMatchJSON.beatmapId = matchData.beatmapId;
this.inProgress = MatchData.inProgress; this.beatmapChecksum = this.cachedMatchJSON.beatmapChecksum = matchData.beatmapChecksum;
this.matchType = MatchData.matchType; 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.activeMods = MatchData.activeMods; this.playMode = matchData.playMode;
const gameNameChanged = this.gameName !== MatchData.gameName; this.matchScoringType = matchData.matchScoringType;
this.gameName = MatchData.gameName; this.matchTeamType = matchData.matchTeamType;
this.specialModes = matchData.specialModes;
if (MatchData.gamePassword == '') MatchData.gamePassword == null; const gameSeedChanged = this.seed !== matchData.seed;
this.gamePassword = MatchData.gamePassword; this.seed = matchData.seed;
this.beatmapName = MatchData.beatmapName;
this.beatmapId = MatchData.beatmapId;
this.beatmapChecksum = MatchData.beatmapChecksum;
this.host = MatchData.host;
this.playMode = MatchData.playMode;
this.matchScoringType = MatchData.matchScoringType;
this.matchTeamType = MatchData.matchTeamType;
this.specialModes = MatchData.specialModes;
const gameSeedChanged = this.seed !== MatchData.seed;
this.seed = MatchData.seed;
if (gameNameChanged || gameSeedChanged) { if (gameNameChanged || gameSeedChanged) {
const queryData = []; const queryData = [];
if (gameNameChanged) { if (gameNameChanged) {
queryData.push(MatchData.gameName); queryData.push(matchData.gameName);
} }
if (gameSeedChanged) { if (gameSeedChanged) {
queryData.push(MatchData.seed); queryData.push(matchData.seed);
} }
queryData.push(this.matchId); 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(); this.sendMatchUpdate();
// Update the match listing in the lobby to reflect these changes // 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; const osuPacketWriter = new osu.Bancho.Writer;
osuPacketWriter.MatchUpdate(this.createOsuMatchJSON()); osuPacketWriter.MatchUpdate(this.generateMatchJSON());
// Update all users in the match with new match information // Update all users in the match with new match information
if (Streams.exists(this.matchStreamName)) this.matchStream.Send(osuPacketWriter.toBuffer);
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
} }
moveToSlot(MatchUser = new User, SlotToMoveTo) { /*moveToSlot(MatchUser = new User, SlotToMoveTo) {
const oldSlot = this.slots[MatchUser.matchSlotId]; const oldSlot = this.slots[MatchUser.matchSlotId];
// Set the new slot's data to the user's old slot data // Set the new slot's data to the user's old slot data

View file

@ -1,13 +1,16 @@
import { SlotStatus } from "../enums/SlotStatus";
import { User } from "./User";
export class Slot { export class Slot {
public status:number; public status:SlotStatus;
public team:number; public team:number;
public player:number; // playerId public player?:User; // playerId
public mods:number; public mods:number;
public constructor() { public constructor(status:SlotStatus, team:number, player?:User, mods:number = 0) {
this.status = 0; this.status = status;
this.team = 0; this.team = team;
this.player = 0; this.player = player;
this.mods = 0; this.mods = mods;
} }
} }

View file

@ -5,7 +5,7 @@ export function ChangeAction(user:User, data:any) {
user.updatePresence(data); user.updatePresence(data);
if (user.spectatorStream != null) { if (user.spectatorStream != null) {
const statusUpdate = StatusUpdate(user, user.id, false); const statusUpdate = StatusUpdate(user.sharedContent, user.id);
user.spectatorStream.Send(statusUpdate); user.spectatorStream.Send(statusUpdate);
} }
} }

View file

@ -1,15 +1,22 @@
import { SharedContent } from "../BanchoServer";
import { RankingModes } from "../enums/RankingModes"; import { RankingModes } from "../enums/RankingModes";
import { User } from "../objects/User"; import { User } from "../objects/User";
const osu = require("osu-packet"); 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 if (id == 3) return; // Ignore Bot
// Create new osu packet writer // Create new osu packet writer
const osuPacketWriter = new osu.Bancho.Writer; const osuPacketWriter = new osu.Bancho.Writer;
let sharedContent:SharedContent;
if (arg0 instanceof User) {
sharedContent = arg0.sharedContent;
} else {
sharedContent = arg0;
}
// Get user's class // Get user's class
const userData = user.sharedContent.users.getById(id); const userData = sharedContent.users.getById(id);
if (userData == null) return; if (userData == null) return;
@ -32,6 +39,9 @@ export function StatusUpdate(user:User, id:number, sendImmidiate:boolean = true)
osuPacketWriter.HandleOsuUpdate(UserStatusObject); osuPacketWriter.HandleOsuUpdate(UserStatusObject);
// Send data to user's queue // Send data to user's queue
if (sendImmidiate) user.addActionToQueue(osuPacketWriter.toBuffer); if (arg0 instanceof User) {
else return osuPacketWriter.toBuffer; arg0.addActionToQueue(osuPacketWriter.toBuffer);
}
return osuPacketWriter.toBuffer;
} }

View file

@ -1,10 +1,17 @@
import { SharedContent } from "../BanchoServer";
import { User } from "../objects/User"; import { User } from "../objects/User";
const osu = require("osu-packet"); 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; 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; if (userData == null) return;
@ -19,6 +26,9 @@ export function UserPresence(user:User, id:number, sendImmidiate:boolean = true)
rank: userData.rank rank: userData.rank
}); });
if (sendImmidiate) userData.addActionToQueue(osuPacketWriter.toBuffer); if (arg0 instanceof User) {
else return osuPacketWriter.toBuffer; arg0.addActionToQueue(osuPacketWriter.toBuffer);
}
return osuPacketWriter.toBuffer;
} }

View file

@ -1,17 +1,27 @@
import { SharedContent } from "../BanchoServer";
import { User } from "../objects/User"; import { User } from "../objects/User";
const osu = require("osu-packet"); 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; const osuPacketWriter = new osu.Bancho.Writer;
let sharedContent:SharedContent;
if (arg0 instanceof User) {
sharedContent = arg0.sharedContent;
} else {
sharedContent = arg0;
}
let userIds:Array<number> = new Array<number>(); let userIds:Array<number> = new Array<number>();
for (let userData of user.sharedContent.users.getIterableItems()) { for (let userData of sharedContent.users.getIterableItems()) {
userIds.push(userData.id); userIds.push(userData.id);
} }
osuPacketWriter.UserPresenceBundle(userIds); osuPacketWriter.UserPresenceBundle(userIds);
if (sendImmidiate) user.addActionToQueue(osuPacketWriter.toBuffer); if (arg0 instanceof User) {
else return osuPacketWriter.toBuffer; arg0.addActionToQueue(osuPacketWriter.toBuffer);
}
return osuPacketWriter.toBuffer;
} }