MULTIPLAYER WORKS KINDA!
This commit is contained in:
parent
be52b19002
commit
f08e34dc82
24 changed files with 554 additions and 305 deletions
|
@ -11,7 +11,8 @@ if (!existsSync("./config.json")) {
|
||||||
import { ChatHistory } from "./server/ChatHistory";
|
import { ChatHistory } from "./server/ChatHistory";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
import express from "express";
|
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";
|
import { Registry, collectDefaultMetrics } from "prom-client";
|
||||||
const config:any = JSON.parse(readFileSync(__dirname + "/config.json").toString());
|
const config:any = JSON.parse(readFileSync(__dirname + "/config.json").toString());
|
||||||
// Pull out shared data from BanchoServer
|
// Pull out shared data from BanchoServer
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export abstract class Constants {
|
export abstract class Constants {
|
||||||
public static readonly DEBUG = true;
|
public static readonly DEBUG = false;
|
||||||
}
|
}
|
|
@ -13,17 +13,12 @@ import { UserArray } from "./objects/UserArray";
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
import { DataStreamArray } from "./objects/DataStreamArray";
|
import { DataStreamArray } from "./objects/DataStreamArray";
|
||||||
import { MultiplayerManager } from "./MultiplayerManager";
|
import { MultiplayerManager } from "./MultiplayerManager";
|
||||||
|
import { SharedContent } from "./interfaces/SharedContent";
|
||||||
const config:any = JSON.parse(readFileSync("./config.json").toString());
|
const config:any = JSON.parse(readFileSync("./config.json").toString());
|
||||||
// TODO: Port osu-packet to TypeScript
|
// TODO: Port osu-packet to TypeScript
|
||||||
const osu = require("osu-packet");
|
const osu = require("osu-packet");
|
||||||
|
|
||||||
export interface SharedContent {
|
|
||||||
chatManager:ChatManager,
|
|
||||||
database:Database,
|
|
||||||
mutiplayerManager:MultiplayerManager,
|
|
||||||
streams:DataStreamArray,
|
|
||||||
users:UserArray,
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharedContent:any = {};
|
const sharedContent:any = {};
|
||||||
// NOTE: This function should only be used externaly in Binato.ts and in this file.
|
// 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
|
// User session storage
|
||||||
const users:UserArray = sharedContent.users = new UserArray();
|
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
|
// DataStream storage
|
||||||
const streams:DataStreamArray = sharedContent.streams = new DataStreamArray();
|
const streams:DataStreamArray = sharedContent.streams = new DataStreamArray();
|
||||||
|
|
||||||
// ChatManager
|
// 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("osu", "The main channel", true);
|
||||||
chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
|
chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
|
||||||
chatManager.AddChatChannel("english", "Talk in exclusively English");
|
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());
|
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;
|
let redisClient:RedisClientType;
|
||||||
|
|
||||||
async function subscribeToChannel(channelName:string, callback:(message:string) => void) {
|
async function subscribeToChannel(channelName:string, callback:(message:string) => void) {
|
||||||
|
@ -101,6 +96,7 @@ import { Logout } from "./packets/Logout";
|
||||||
import { UserPresence } from "./packets/UserPresence";
|
import { UserPresence } from "./packets/UserPresence";
|
||||||
import { UserStatsRequest } from "./packets/UserStatsRequest";
|
import { UserStatsRequest } from "./packets/UserStatsRequest";
|
||||||
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
||||||
|
import { Match } from "./objects/Match";
|
||||||
|
|
||||||
// User timeout interval
|
// User timeout interval
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
@ -190,11 +186,11 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_JoinLobby:
|
case Packets.Client_JoinLobby:
|
||||||
//multiplayerManager.userEnterLobby(PacketUser);
|
multiplayerManager.JoinLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_PartLobby:
|
case Packets.Client_PartLobby:
|
||||||
//multiplayerManager.userLeaveLobby(PacketUser);
|
multiplayerManager.LeaveLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_CreateMatch:
|
case Packets.Client_CreateMatch:
|
||||||
|
@ -202,76 +198,76 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_JoinMatch:
|
case Packets.Client_JoinMatch:
|
||||||
//multiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
|
multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchChangeSlot:
|
case Packets.Client_MatchChangeSlot:
|
||||||
//PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
|
PacketUser.match?.moveToSlot(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchReady:
|
case Packets.Client_MatchReady:
|
||||||
//PacketUser.currentMatch.setStateReady(PacketUser);
|
PacketUser.match?.setStateReady(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchChangeSettings:
|
case Packets.Client_MatchChangeSettings:
|
||||||
//await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
|
await PacketUser.match?.updateMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchNotReady:
|
case Packets.Client_MatchNotReady:
|
||||||
//PacketUser.currentMatch.setStateNotReady(PacketUser);
|
PacketUser.match?.setStateNotReady(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// TODO: Match leave so the matches actually close
|
||||||
case Packets.Client_PartMatch:
|
case Packets.Client_PartMatch:
|
||||||
//await multiplayerManager.leaveMultiplayerMatch(PacketUser);
|
//await multiplayerManager.leaveMultiplayerMatch(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Also handles user kick if the slot has a user
|
|
||||||
case Packets.Client_MatchLock:
|
case Packets.Client_MatchLock:
|
||||||
//PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
|
PacketUser.match?.lockOrKick(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchNoBeatmap:
|
case Packets.Client_MatchNoBeatmap:
|
||||||
//PacketUser.currentMatch.missingBeatmap(PacketUser);
|
PacketUser.match?.missingBeatmap(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchSkipRequest:
|
case Packets.Client_MatchSkipRequest:
|
||||||
//PacketUser.currentMatch.matchSkip(PacketUser);
|
PacketUser.match?.matchSkip(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchHasBeatmap:
|
case Packets.Client_MatchHasBeatmap:
|
||||||
//PacketUser.currentMatch.notMissingBeatmap(PacketUser);
|
PacketUser.match?.notMissingBeatmap(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchTransferHost:
|
case Packets.Client_MatchTransferHost:
|
||||||
//PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
|
PacketUser.match?.transferHost(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchChangeMods:
|
case Packets.Client_MatchChangeMods:
|
||||||
//PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
|
PacketUser.match?.updateMods(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchStart:
|
case Packets.Client_MatchStart:
|
||||||
//PacketUser.currentMatch.startMatch();
|
PacketUser.match?.startMatch();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchLoadComplete:
|
case Packets.Client_MatchLoadComplete:
|
||||||
//PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
|
PacketUser.match?.matchPlayerLoaded(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchComplete:
|
case Packets.Client_MatchComplete:
|
||||||
//await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
|
await PacketUser.match?.onPlayerFinishMatch(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchScoreUpdate:
|
case Packets.Client_MatchScoreUpdate:
|
||||||
//PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
|
PacketUser.match?.updatePlayerScore(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchFailed:
|
case Packets.Client_MatchFailed:
|
||||||
//PacketUser.currentMatch.matchFailed(PacketUser);
|
PacketUser.match?.matchFailed(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchChangeTeam:
|
case Packets.Client_MatchChangeTeam:
|
||||||
//PacketUser.currentMatch.changeTeam(PacketUser);
|
PacketUser.match?.changeTeam(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_ChannelJoin:
|
case Packets.Client_ChannelJoin:
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import { Channel } from "./objects/Channel";
|
import { Channel } from "./objects/Channel";
|
||||||
import { ConsoleHelper } from "../ConsoleHelper";
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
import { FunkyArray } from "./objects/FunkyArray";
|
import { FunkyArray } from "./objects/FunkyArray";
|
||||||
import { DataStreamArray } from "./objects/DataStreamArray";
|
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
|
import { SharedContent } from "./interfaces/SharedContent";
|
||||||
const osu = require("osu-packet");
|
const osu = require("osu-packet");
|
||||||
|
|
||||||
export class ChatManager {
|
export class ChatManager {
|
||||||
public chatChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
public chatChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
||||||
public forceJoinChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
public forceJoinChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
||||||
public streams:DataStreamArray;
|
private readonly sharedContent:SharedContent;
|
||||||
|
|
||||||
public constructor(streams:DataStreamArray) {
|
public constructor(sharedContent:SharedContent) {
|
||||||
this.streams = streams;
|
this.sharedContent = sharedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddChatChannel(name:string, description:string, forceJoin:boolean = false) : Channel {
|
public AddChatChannel(name:string, description:string, forceJoin:boolean = false) : Channel {
|
||||||
const stream = this.streams.CreateStream(`chat_channel:${name}`, false);
|
const stream = this.sharedContent.streams.CreateStream(`chat_channel:${name}`, false);
|
||||||
const channel = new Channel(`#${name}`, description, stream);
|
const channel = new Channel(this.sharedContent, `#${name}`, description, stream);
|
||||||
this.chatChannels.add(channel.name, channel);
|
this.chatChannels.add(channel.name, channel);
|
||||||
if (forceJoin) {
|
if (forceJoin) {
|
||||||
this.forceJoinChannels.add(name, channel);
|
this.forceJoinChannels.add(name, channel);
|
||||||
|
@ -26,8 +26,8 @@ export class ChatManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddSpecialChatChannel(name:string, streamName:string, forceJoin:boolean = false) : Channel {
|
public AddSpecialChatChannel(name:string, streamName:string, forceJoin:boolean = false) : Channel {
|
||||||
const stream = this.streams.CreateStream(`chat_channel:${streamName}`, false);
|
const stream = this.sharedContent.streams.CreateStream(`chat_channel:${streamName}`, false);
|
||||||
const channel = new Channel(`#${name}`, "", stream);
|
const channel = new Channel(this.sharedContent, `#${name}`, "", stream);
|
||||||
this.chatChannels.add(channel.name, channel);
|
this.chatChannels.add(channel.name, channel);
|
||||||
if (forceJoin) {
|
if (forceJoin) {
|
||||||
this.forceJoinChannels.add(name, channel);
|
this.forceJoinChannels.add(name, channel);
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { ChatManager } from "./ChatManager";
|
||||||
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
||||||
import { UserPresence } from "./packets/UserPresence";
|
import { UserPresence } from "./packets/UserPresence";
|
||||||
import { StatusUpdate } from "./packets/StatusUpdate";
|
import { StatusUpdate } from "./packets/StatusUpdate";
|
||||||
import { SharedContent } from "./BanchoServer";
|
import { SharedContent } from "./interfaces/SharedContent";
|
||||||
const config:any = JSON.parse(readFileSync("./config.json").toString());
|
const config:any = JSON.parse(readFileSync("./config.json").toString());
|
||||||
const { decrypt: aesDecrypt } = require("aes256");
|
const { decrypt: aesDecrypt } = require("aes256");
|
||||||
const osu = require("osu-packet");
|
const osu = require("osu-packet");
|
||||||
|
|
|
@ -1,28 +1,104 @@
|
||||||
import { SharedContent } from "./BanchoServer";
|
import { Channel } from "./objects/Channel";
|
||||||
|
import { SharedContent } from "./interfaces/SharedContent";
|
||||||
import { SlotStatus } from "./enums/SlotStatus";
|
import { SlotStatus } from "./enums/SlotStatus";
|
||||||
import { DataStream } from "./objects/DataStream";
|
import { DataStream } from "./objects/DataStream";
|
||||||
import { DataStreamArray } from "./objects/DataStreamArray";
|
import { Match } from "./objects/Match";
|
||||||
import { FunkyArray } from "./objects/FunkyArray";
|
|
||||||
import { Match, MatchData } from "./objects/Match";
|
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
import { StatusUpdate } from "./packets/StatusUpdate";
|
import { StatusUpdate } from "./packets/StatusUpdate";
|
||||||
import { UserPresence } from "./packets/UserPresence";
|
import { UserPresence } from "./packets/UserPresence";
|
||||||
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
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");
|
const osu = require("osu-packet");
|
||||||
|
|
||||||
export class MultiplayerManager {
|
export class MultiplayerManager {
|
||||||
private readonly sharedContent:SharedContent;
|
private readonly sharedContent:SharedContent;
|
||||||
private matches:FunkyArray<Match> = new FunkyArray<Match>();
|
private matches:MatchArray = new MatchArray();
|
||||||
private readonly lobbyStream:DataStream;
|
private readonly lobbyStream:DataStream;
|
||||||
|
private readonly lobbyChat:Channel;
|
||||||
|
|
||||||
public constructor(sharedContent:SharedContent) {
|
public constructor(sharedContent:SharedContent) {
|
||||||
this.sharedContent = sharedContent;
|
this.sharedContent = sharedContent;
|
||||||
this.lobbyStream = sharedContent.streams.CreateStream("multiplayer:lobby", false);
|
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) {
|
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) {
|
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);
|
||||||
|
this.JoinMatch(user, match.matchId);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// TODO: Mods enum
|
// TODO: Mods enum
|
||||||
export enum Mods {
|
export enum Mods {
|
||||||
|
None
|
||||||
}
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
export enum SlotStatus {
|
export enum SlotStatus {
|
||||||
Unknown_0,
|
Empty = 1,
|
||||||
Empty,
|
Locked = 2,
|
||||||
Locked,
|
NotReady = 4,
|
||||||
NotReady,
|
Ready = 8,
|
||||||
|
MissingBeatmap = 16,
|
||||||
|
Playing = 32,
|
||||||
|
Quit = 128
|
||||||
}
|
}
|
4
server/enums/Team.ts
Normal file
4
server/enums/Team.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export enum Team {
|
||||||
|
Red,
|
||||||
|
Blue
|
||||||
|
}
|
20
server/interfaces/MatchData.ts
Normal file
20
server/interfaces/MatchData.ts
Normal file
|
@ -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<MatchDataSlot>,
|
||||||
|
host:number,
|
||||||
|
playMode:number,
|
||||||
|
matchScoringType:number,
|
||||||
|
matchTeamType:number,
|
||||||
|
specialModes:number,
|
||||||
|
seed:number
|
||||||
|
}
|
6
server/interfaces/MatchDataSlot.ts
Normal file
6
server/interfaces/MatchDataSlot.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface MatchDataSlot {
|
||||||
|
status:number,
|
||||||
|
team:number,
|
||||||
|
playerId:number,
|
||||||
|
mods:number | undefined,
|
||||||
|
}
|
4
server/interfaces/MatchJoinData.ts
Normal file
4
server/interfaces/MatchJoinData.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface MatchJoinData {
|
||||||
|
matchId: number,
|
||||||
|
gamePassword: string
|
||||||
|
}
|
4
server/interfaces/MatchStartSkipData.ts
Normal file
4
server/interfaces/MatchStartSkipData.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface MatchStartSkipData {
|
||||||
|
playerId:number,
|
||||||
|
flag:boolean
|
||||||
|
}
|
11
server/interfaces/PlayerScore.ts
Normal file
11
server/interfaces/PlayerScore.ts
Normal file
|
@ -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
|
||||||
|
}
|
13
server/interfaces/SharedContent.ts
Normal file
13
server/interfaces/SharedContent.ts
Normal file
|
@ -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,
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SharedContent } from "../interfaces/SharedContent";
|
||||||
import { DataStream } from "./DataStream";
|
import { DataStream } from "./DataStream";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
const osu = require("osu-packet");
|
const osu = require("osu-packet");
|
||||||
|
@ -9,11 +10,23 @@ export class Channel {
|
||||||
private isLocked:boolean = false;
|
private isLocked:boolean = false;
|
||||||
private _isSpecial: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.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this._isSpecial = isSpecial;
|
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() {
|
public get isSpecial() {
|
||||||
|
@ -35,6 +48,11 @@ export class Channel {
|
||||||
if (message.split(" ")[0] === "!lock") {
|
if (message.split(" ")[0] === "!lock") {
|
||||||
this.isLocked = true;
|
this.isLocked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message === "!mp start") {
|
||||||
|
this.SendBotMessage("glhf!");
|
||||||
|
sender.match?.startMatch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
@ -47,6 +65,22 @@ export class Channel {
|
||||||
this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender);
|
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) {
|
public SendSystemMessage(message:string, sendTo?:User) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
osuPacketWriter.SendMessage({
|
osuPacketWriter.SendMessage({
|
||||||
|
|
|
@ -64,7 +64,7 @@ export class DataStream {
|
||||||
user.addActionToQueue(data);
|
user.addActionToQueue(data);
|
||||||
}
|
}
|
||||||
if (Constants.DEBUG) {
|
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}]`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,18 @@
|
||||||
import { Channel } from "./Channel";
|
import { Channel } from "./Channel";
|
||||||
import { SharedContent } from "../BanchoServer";
|
import { SharedContent } from "../interfaces/SharedContent";
|
||||||
import { DataStream } from "./DataStream";
|
import { DataStream } from "./DataStream";
|
||||||
import { Slot } from "./Slot";
|
import { Slot } from "./Slot";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
import { StatusUpdate } from "../packets/StatusUpdate";
|
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");
|
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<MatchDataSlot>,
|
|
||||||
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 {
|
export class Match {
|
||||||
// osu! Data
|
// osu! Data
|
||||||
public matchId:number = -1;
|
public matchId:number = -1;
|
||||||
|
@ -58,12 +38,16 @@ export class Match {
|
||||||
public matchStream:DataStream;
|
public matchStream:DataStream;
|
||||||
public matchChatChannel:Channel;
|
public matchChatChannel:Channel;
|
||||||
|
|
||||||
|
public matchLoadSlots?:Array<MatchStartSkipData>;
|
||||||
|
public matchSkippedSlots?:Array<MatchStartSkipData>;
|
||||||
|
|
||||||
|
public playerScores?:Array<PlayerScore>;
|
||||||
|
|
||||||
private cachedMatchJSON:MatchData;
|
private cachedMatchJSON:MatchData;
|
||||||
private readonly sharedContent:SharedContent;
|
private readonly sharedContent:SharedContent;
|
||||||
|
|
||||||
private constructor(matchData:MatchData, sharedContent:SharedContent) {
|
private constructor(matchData:MatchData, sharedContent:SharedContent) {
|
||||||
this.sharedContent = sharedContent;
|
this.sharedContent = sharedContent;
|
||||||
console.log(matchData);
|
|
||||||
this.matchId = matchData.matchId;
|
this.matchId = matchData.matchId;
|
||||||
|
|
||||||
this.inProgress = matchData.inProgress;
|
this.inProgress = matchData.inProgress;
|
||||||
|
@ -80,11 +64,12 @@ export class Match {
|
||||||
this.beatmapId = matchData.beatmapId;
|
this.beatmapId = matchData.beatmapId;
|
||||||
this.beatmapChecksum = matchData.beatmapChecksum;
|
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) {
|
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 {
|
} 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.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.matchChatChannel = sharedContent.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`);
|
||||||
|
|
||||||
this.cachedMatchJSON = matchData;
|
this.cachedMatchJSON = matchData;
|
||||||
|
|
||||||
//this.matchLoadSlots = null;
|
|
||||||
//this.matchSkippedSlots = null;
|
|
||||||
|
|
||||||
//this.playerScores = null;
|
//this.playerScores = null;
|
||||||
|
|
||||||
//this.multiplayerExtras = null;
|
//this.multiplayerExtras = null;
|
||||||
|
@ -130,8 +112,6 @@ export class Match {
|
||||||
|
|
||||||
const matchInstance = new Match(matchData, sharedContent);
|
const matchInstance = new Match(matchData, sharedContent);
|
||||||
|
|
||||||
console.log(matchInstance.matchId);
|
|
||||||
|
|
||||||
// Update the status of the current user
|
// Update the status of the current user
|
||||||
StatusUpdate(matchHost, matchHost.id);
|
StatusUpdate(matchHost, matchHost.id);
|
||||||
|
|
||||||
|
@ -141,8 +121,7 @@ export class Match {
|
||||||
|
|
||||||
matchHost.addActionToQueue(osuPacketWriter.toBuffer);
|
matchHost.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
// Update the match listing for users in the multiplayer lobby
|
sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||||
//global.MultiplayerManager.updateMatchListing();
|
|
||||||
|
|
||||||
resolve(matchInstance);
|
resolve(matchInstance);
|
||||||
} catch (e) {
|
} 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
|
// 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++) {
|
for (let i = 0; i < this.slots.length; i++) {
|
||||||
const slot = this.slots[i];
|
const slot = this.slots[i];
|
||||||
const osuSlot = this.cachedMatchJSON.slots[i];
|
const osuSlot = this.cachedMatchJSON.slots[i];
|
||||||
|
@ -177,33 +149,27 @@ export class Match {
|
||||||
return this.cachedMatchJSON;
|
return this.cachedMatchJSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*leaveMatch(MatchUser = new User) {
|
public leaveMatch(user:User) {
|
||||||
// Make sure this leave call is valid
|
// Make sure this leave call is valid
|
||||||
if (!MatchUser.inMatch) return;
|
if (!user.inMatch || user.matchSlot === undefined) {
|
||||||
|
return;
|
||||||
// Get the user's slot
|
}
|
||||||
const slot = this.slots[MatchUser.matchSlotId];
|
|
||||||
|
|
||||||
// Set the slot's status to avaliable
|
// Set the slot's status to avaliable
|
||||||
slot.playerId = -1;
|
user.matchSlot.status = SlotStatus.Empty;
|
||||||
slot.status = 1;
|
user.matchSlot.team = 0;
|
||||||
|
user.matchSlot.player = undefined;
|
||||||
|
user.matchSlot.mods = 0;
|
||||||
|
|
||||||
// Remove the leaving user from the match's stream
|
// Remove the leaving user from the match's stream
|
||||||
Streams.removeUserFromStream(this.matchStreamName, MatchUser.uuid);
|
this.matchStream.RemoveUser(user);
|
||||||
Streams.removeUserFromStream(this.matchChatStreamName, MatchUser.uuid);
|
this.matchChatChannel.Leave(user);
|
||||||
|
|
||||||
// Send this after removing the user from match streams to avoid a leave notification for self
|
// Send this after removing the user from match streams to avoid a leave notification for self
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
public async updateMatch(user:User, matchData:MatchData) {
|
||||||
|
|
||||||
// Remove user from the multiplayer channel for the match
|
|
||||||
osuPacketWriter.ChannelRevoked("#multiplayer");
|
|
||||||
|
|
||||||
MatchUser.addActionToQueue(osuPacketWriter.toBuffer);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
async updateMatch(user:User, matchData:MatchData) {
|
|
||||||
// Update match with new data
|
// Update match with new data
|
||||||
this.inProgress = matchData.inProgress;
|
this.inProgress = matchData.inProgress;
|
||||||
|
|
||||||
|
@ -228,21 +194,20 @@ export class Match {
|
||||||
const hostUser = this.sharedContent.users.getById(matchData.host);
|
const hostUser = this.sharedContent.users.getById(matchData.host);
|
||||||
if (hostUser === undefined) {
|
if (hostUser === undefined) {
|
||||||
// NOTE: This should never be possible to hit
|
// NOTE: This should never be possible to hit
|
||||||
// since this user JUST made the match.
|
|
||||||
throw "Host User of match was undefined";
|
throw "Host User of match was undefined";
|
||||||
}
|
}
|
||||||
this.host = hostUser;
|
this.host = hostUser;
|
||||||
this.cachedMatchJSON.host = this.host.id;
|
this.cachedMatchJSON.host = this.host.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.playMode = matchData.playMode;
|
this.playMode = this.cachedMatchJSON.playMode = matchData.playMode;
|
||||||
|
|
||||||
this.matchScoringType = matchData.matchScoringType;
|
this.matchScoringType = this.cachedMatchJSON.matchScoringType = matchData.matchScoringType;
|
||||||
this.matchTeamType = matchData.matchTeamType;
|
this.matchTeamType = this.cachedMatchJSON.matchTeamType = matchData.matchTeamType;
|
||||||
this.specialModes = matchData.specialModes;
|
this.specialModes = this.cachedMatchJSON.specialModes = matchData.specialModes;
|
||||||
|
|
||||||
const gameSeedChanged = this.seed !== matchData.seed;
|
const gameSeedChanged = this.seed !== matchData.seed;
|
||||||
this.seed = matchData.seed;
|
this.seed = this.cachedMatchJSON.seed = matchData.seed;
|
||||||
|
|
||||||
if (gameNameChanged || gameSeedChanged) {
|
if (gameNameChanged || gameSeedChanged) {
|
||||||
const queryData = [];
|
const queryData = [];
|
||||||
|
@ -260,7 +225,7 @@ export class Match {
|
||||||
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();
|
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendMatchUpdate() {
|
public sendMatchUpdate() {
|
||||||
|
@ -272,126 +237,144 @@ export class Match {
|
||||||
this.matchStream.Send(osuPacketWriter.toBuffer);
|
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*moveToSlot(MatchUser = new User, SlotToMoveTo) {
|
public moveToSlot(user:User, slotToMoveTo:number) {
|
||||||
const oldSlot = this.slots[MatchUser.matchSlotId];
|
if (slotToMoveTo < 0 || slotToMoveTo >= this.slots.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set the new slot's data to the user's old slot data
|
const newSlot = this.slots[slotToMoveTo];
|
||||||
this.slots[SlotToMoveTo].playerId = MatchUser.id;
|
if (newSlot.status === SlotStatus.Locked || !(user.matchSlot instanceof Slot)) {
|
||||||
MatchUser.matchSlotId = SlotToMoveTo;
|
return;
|
||||||
this.slots[SlotToMoveTo].status = 4;
|
}
|
||||||
|
|
||||||
// Set the old slot's data to open
|
user.matchSlot = newSlot.transferFrom(user.matchSlot);
|
||||||
oldSlot.playerId = -1;
|
|
||||||
oldSlot.status = 1;
|
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
|
||||||
// Update the match listing in the lobby to reflect this change
|
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||||
global.MultiplayerManager.updateMatchListing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTeam(MatchUser = new User) {
|
public changeTeam(user:User) {
|
||||||
const slot = this.slots[MatchUser.matchSlotId];
|
if (!(user.matchSlot instanceof Slot)) {
|
||||||
slot.team = slot.team == 0 ? 1 : 0;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.matchSlot.team = user.matchSlot.team === Team.Red ? Team.Blue : Team.Red;
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateReady(MatchUser = new User) {
|
public setStateReady(user:User) {
|
||||||
if (!MatchUser.inMatch) return;
|
if (!(user.matchSlot instanceof Slot)) {
|
||||||
|
return;
|
||||||
// Set the user's ready state to ready
|
}
|
||||||
this.slots[MatchUser.matchSlotId].status = 8;
|
|
||||||
|
user.matchSlot.status = SlotStatus.Ready;
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateNotReady(MatchUser = new User) {
|
public setStateNotReady(user:User) {
|
||||||
if (!MatchUser.inMatch) return;
|
if (!(user.matchSlot instanceof Slot)) {
|
||||||
|
return;
|
||||||
// Set the user's ready state to not ready
|
}
|
||||||
this.slots[MatchUser.matchSlotId].status = 4;
|
|
||||||
|
user.matchSlot.status = SlotStatus.NotReady;
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
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
|
// Make sure the user attempting to kick / lock is the host of the match
|
||||||
if (this.host != MatchUser.id) return;
|
if (User.Equals(user, this.host)) {
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
const slot = this.slots[slotToActionOn];
|
||||||
|
if (slot.player instanceof Slot) {
|
||||||
// Update the match listing in the lobby listing to reflect this change
|
// Kick
|
||||||
global.MultiplayerManager.updateMatchListing();
|
if (User.Equals(user, slot.player)) {
|
||||||
|
return;
|
||||||
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 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) {
|
public missingBeatmap(user:User) {
|
||||||
// User is missing the beatmap set the status to reflect it
|
const slot = user.matchSlot;
|
||||||
this.slots[MatchUser.matchSlotId].status = 16;
|
if (!(slot instanceof Slot)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.status = SlotStatus.MissingBeatmap;
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
notMissingBeatmap(MatchUser = new User) {
|
public notMissingBeatmap(user:User) {
|
||||||
// The user is not missing the beatmap, set the status to normal
|
const slot = user.matchSlot;
|
||||||
this.slots[MatchUser.matchSlotId].status = 4;
|
if (!(slot instanceof Slot)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.status = SlotStatus.NotReady;
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
matchSkip(MatchUser = new User) {
|
public matchSkip(user:User) {
|
||||||
if (this.matchSkippedSlots == null) {
|
if (this.matchSkippedSlots === undefined) {
|
||||||
this.matchSkippedSlots = [];
|
this.matchSkippedSlots = new Array<MatchStartSkipData>();
|
||||||
|
|
||||||
const skippedSlots = this.matchSkippedSlots;
|
|
||||||
|
|
||||||
for (let slot of this.slots) {
|
for (let slot of this.slots) {
|
||||||
// Make sure the slot has a user in it
|
// 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
|
// 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;
|
let allSkipped = true;
|
||||||
for (let skippedSlot of this.matchSkippedSlots) {
|
for (let skippedSlot of this.matchSkippedSlots) {
|
||||||
// If loadslot belongs to this user then set loaded to true
|
// If loadslot belongs to this user then set loaded to true
|
||||||
if (skippedSlot.playerId == MatchUser.id) {
|
if (skippedSlot.playerId === user.id) {
|
||||||
skippedSlot.skipped = true;
|
skippedSlot.flag = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (skippedSlot.skipped) continue;
|
if (skippedSlot.flag) continue;
|
||||||
|
|
||||||
// A user hasn't skipped
|
// A user hasn't skipped
|
||||||
allSkipped = false;
|
allSkipped = false;
|
||||||
|
@ -401,66 +384,77 @@ export class Match {
|
||||||
if (allSkipped) {
|
if (allSkipped) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
|
||||||
osuPacketWriter.MatchPlayerSkipped(MatchUser.id);
|
osuPacketWriter.MatchPlayerSkipped(user.id);
|
||||||
osuPacketWriter.MatchSkip();
|
osuPacketWriter.MatchSkip();
|
||||||
|
|
||||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
this.matchSkippedSlots = null;
|
this.matchSkippedSlots = undefined;
|
||||||
} else {
|
} else {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
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
|
// 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
|
// TODO: Fix not being able to add DT when freemod is active
|
||||||
updateMods(MatchUser = new User, MatchMods) {
|
public updateMods(user:User, mods:Mods) {
|
||||||
// Check if freemod is enabled
|
const slot = user.matchSlot;
|
||||||
|
if (!(slot instanceof Slot)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if freemod is enabled or not
|
||||||
if (this.specialModes === 1) {
|
if (this.specialModes === 1) {
|
||||||
this.slots[MatchUser.matchSlotId].mods = MatchMods;
|
slot.mods = mods;
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
} else {
|
} else {
|
||||||
// Make sure the person updating mods is the host of the match
|
if (!User.Equals(this.host, user)) {
|
||||||
if (this.host !== MatchUser.id) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Change the matches mods to these new mods
|
this.activeMods = mods;
|
||||||
// TODO: Do this per user if freemod is enabled
|
|
||||||
this.activeMods = MatchMods;
|
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update match listing in the lobby to reflect this change
|
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||||
global.MultiplayerManager.updateMatchListing();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
startMatch() {
|
startMatch() {
|
||||||
// Make sure the match is not already in progress
|
// Make sure the match is not already in progress
|
||||||
// The client sometimes double fires the start packet
|
// The client sometimes double fires the start packet
|
||||||
if (this.inProgress) return;
|
if (this.inProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.inProgress = true;
|
this.inProgress = true;
|
||||||
// Create array for monitoring users until they are ready to play
|
|
||||||
this.matchLoadSlots = [];
|
this.matchLoadSlots = new Array<MatchStartSkipData>();
|
||||||
// Loop through all slots in the match
|
// Loop through all slots in the match
|
||||||
for (let slot of this.slots) {
|
for (let slot of this.slots) {
|
||||||
// Make sure the slot has a user in it
|
// 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
|
// Add the slot's user to the loaded checking array
|
||||||
this.matchLoadSlots.push({
|
this.matchLoadSlots.push({
|
||||||
playerId: slot.playerId,
|
playerId: slot.player?.id,
|
||||||
loaded: false
|
flag: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the user's status to playing
|
// Set the user's status to playing
|
||||||
|
@ -469,28 +463,30 @@ export class Match {
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
|
||||||
osuPacketWriter.MatchStart(this.createOsuMatchJSON());
|
osuPacketWriter.MatchStart(this.generateMatchJSON());
|
||||||
|
|
||||||
// Inform all users in the match that it has started
|
// 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
|
// Update all users in the match with new info
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
|
||||||
// Update match listing in lobby to show the game is in progress
|
// Update match listing in lobby to show the game is in progress
|
||||||
global.MultiplayerManager.updateMatchListing();
|
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
matchPlayerLoaded(MatchUser = new User) {
|
public matchPlayerLoaded(user:User) {
|
||||||
// Loop through all user load check items and check if all users are loaded
|
if (this.matchLoadSlots === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let allLoaded = true;
|
let allLoaded = true;
|
||||||
for (let loadedSlot of this.matchLoadSlots) {
|
for (let loadedSlot of this.matchLoadSlots) {
|
||||||
// If loadslot belongs to this user then set loaded to true
|
if (loadedSlot.playerId === user.id) {
|
||||||
if (loadedSlot.playerId == MatchUser.id) {
|
loadedSlot.flag = true;
|
||||||
loadedSlot.loaded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadedSlot.loaded) continue;
|
if (loadedSlot.flag) continue;
|
||||||
|
|
||||||
allLoaded = false;
|
allLoaded = false;
|
||||||
}
|
}
|
||||||
|
@ -499,46 +495,54 @@ export class Match {
|
||||||
if (allLoaded) {
|
if (allLoaded) {
|
||||||
let osuPacketWriter = new osu.Bancho.Writer;
|
let osuPacketWriter = new osu.Bancho.Writer;
|
||||||
osuPacketWriter.MatchAllPlayersLoaded();
|
osuPacketWriter.MatchAllPlayersLoaded();
|
||||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
// Blank out user loading array
|
// Blank out user loading array
|
||||||
this.matchLoadSlots = null;
|
this.matchLoadSlots = undefined;
|
||||||
|
|
||||||
this.playerScores = [];
|
this.playerScores = new Array<PlayerScore>();
|
||||||
for (let i = 0; i < this.slots.length; i++) {
|
for (let slot of this.slots) {
|
||||||
const slot = this.slots[i];
|
if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) {
|
||||||
if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue;
|
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) {
|
public async onPlayerFinishMatch(user:User) {
|
||||||
if (this.matchLoadSlots == null) {
|
if (this.matchLoadSlots === undefined) {
|
||||||
// Repopulate user loading slots again
|
// Repopulate user loading slots again
|
||||||
this.matchLoadSlots = [];
|
this.matchLoadSlots = [];
|
||||||
for (let slot of this.slots) {
|
for (let slot of this.slots) {
|
||||||
// Make sure the slot has a user
|
// 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
|
// Populate user loading slots with this user's id and load status
|
||||||
this.matchLoadSlots.push({
|
this.matchLoadSlots.push({
|
||||||
playerId: slot.playerId,
|
playerId: slot.player?.id,
|
||||||
loaded: false
|
flag: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let allLoaded = true;
|
let allLoaded = true;
|
||||||
|
|
||||||
// Loop through all loaded slots to make sure all users have finished playing
|
|
||||||
for (let loadedSlot of this.matchLoadSlots) {
|
for (let loadedSlot of this.matchLoadSlots) {
|
||||||
if (loadedSlot.playerId == MatchUser.id) {
|
if (loadedSlot.playerId == user.id) {
|
||||||
loadedSlot.loaded = true;
|
loadedSlot.flag = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadedSlot.loaded) continue;
|
if (loadedSlot.flag) continue;
|
||||||
|
|
||||||
// A user hasn't finished playing
|
// A user hasn't finished playing
|
||||||
allLoaded = false;
|
allLoaded = false;
|
||||||
|
@ -548,89 +552,108 @@ export class Match {
|
||||||
if (allLoaded) await this.finishMatch();
|
if (allLoaded) await this.finishMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
async finishMatch() {
|
public finishMatch() {
|
||||||
if (!this.inProgress) return;
|
if (!this.inProgress) {
|
||||||
this.matchLoadSlots = null;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.matchLoadSlots = undefined;
|
||||||
this.inProgress = false;
|
this.inProgress = false;
|
||||||
|
|
||||||
let osuPacketWriter = new osu.Bancho.Writer;
|
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<any> = [
|
||||||
|
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) {
|
for (let slot of this.slots) {
|
||||||
// Make sure the slot has a user
|
// For every empty / locked slot push a null to the data array
|
||||||
if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) {
|
if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) {
|
||||||
queryData.push(null);
|
queryData.push(null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let score = null;
|
|
||||||
for (let _playerScore of this.playerScores) {
|
for (let _playerScore of this.playerScores) {
|
||||||
if (_playerScore.playerId === slot.playerId) {
|
if (_playerScore.player?.id === slot.player?.id) {
|
||||||
score = _playerScore._raw;
|
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;
|
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}` : ""}`);
|
slot.status = SlotStatus.NotReady;
|
||||||
|
|
||||||
// Set the user's status back to normal from playing
|
|
||||||
slot.status = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(queryData);
|
|
||||||
|
|
||||||
osuPacketWriter.MatchComplete();
|
osuPacketWriter.MatchComplete();
|
||||||
|
|
||||||
// Inform all users in the match that it is complete
|
// 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
|
// Update all users in the match with new info
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
|
||||||
// Update match info in the lobby to reflect that the match has finished
|
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
||||||
global.MultiplayerManager.updateMatchListing();
|
|
||||||
|
|
||||||
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;
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
|
||||||
// Make sure the user's slot ID is not invalid
|
if (user.matchSlot === undefined || this.playerScores === undefined) {
|
||||||
if (this.matchSlotId == -1) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the user's current slotID and append it to the givien data, just incase.
|
matchScoreData.id = user.matchSlot;
|
||||||
MatchScoreData.id = MatchPlayer.matchSlotId;
|
|
||||||
|
|
||||||
// Update the playerScores array accordingly
|
// Update playerScores
|
||||||
for (let playerScore of this.playerScores) {
|
for (let playerScore of this.playerScores) {
|
||||||
if (playerScore.playerId == MatchPlayer.id) {
|
if (playerScore.player?.id == user.id) {
|
||||||
playerScore.score = MatchScoreData.totalScore;
|
playerScore.score = matchScoreData.totalScore;
|
||||||
playerScore.isCurrentlyFailed = MatchScoreData.currentHp == 254;
|
const isCurrentlyFailed = matchScoreData.currentHp == 254;
|
||||||
playerScore._raw = MatchScoreData;
|
playerScore.isCurrentlyFailed = isCurrentlyFailed;
|
||||||
|
if (!playerScore.hasFailed && isCurrentlyFailed) {
|
||||||
|
playerScore.hasFailed = true;
|
||||||
|
}
|
||||||
|
playerScore._raw = matchScoreData;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
osuPacketWriter.MatchScoreUpdate(MatchScoreData);
|
osuPacketWriter.MatchScoreUpdate(matchScoreData);
|
||||||
|
|
||||||
// Send the newly updated score to all users in the match
|
// 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;
|
const osuPacketWriter = new osu.Bancho.Writer;
|
||||||
|
|
||||||
// Make sure the user's slot ID is not invalid
|
// Make sure the user is in the match in a valid slot
|
||||||
if (MatchUser.matchSlotId == -1) return;
|
if (user.matchSlot === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
osuPacketWriter.MatchPlayerFailed(MatchUser.id);
|
osuPacketWriter.MatchPlayerFailed(user.id);
|
||||||
|
|
||||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
14
server/objects/MatchArray.ts
Normal file
14
server/objects/MatchArray.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { FunkyArray } from "./FunkyArray";
|
||||||
|
import { Match } from "./Match";
|
||||||
|
|
||||||
|
export class MatchArray extends FunkyArray<Match> {
|
||||||
|
public getById(id:number) : Match | undefined {
|
||||||
|
for (let match of this.getIterableItems()) {
|
||||||
|
if (match.matchId == id) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,48 @@
|
||||||
|
import { Mods } from "../enums/Mods";
|
||||||
import { SlotStatus } from "../enums/SlotStatus";
|
import { SlotStatus } from "../enums/SlotStatus";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
|
|
||||||
export class Slot {
|
export class Slot {
|
||||||
|
public readonly slotId:number;
|
||||||
public status:SlotStatus;
|
public status:SlotStatus;
|
||||||
public team:number;
|
public team:number;
|
||||||
public player?:User; // playerId
|
public player?:User;
|
||||||
public mods:number;
|
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.status = status;
|
||||||
this.team = team;
|
this.team = team;
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.mods = mods;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,8 @@ import { RankingModes } from "../enums/RankingModes";
|
||||||
import { Match } from "./Match";
|
import { Match } from "./Match";
|
||||||
import { DataStream } from "./DataStream";
|
import { DataStream } from "./DataStream";
|
||||||
import { StatusUpdate } from "../packets/StatusUpdate";
|
import { StatusUpdate } from "../packets/StatusUpdate";
|
||||||
import { SharedContent } from "../BanchoServer";
|
import { SharedContent } from "../interfaces/SharedContent";
|
||||||
|
import { Slot } from "./Slot";
|
||||||
|
|
||||||
const rankingModes = [
|
const rankingModes = [
|
||||||
"pp_raw",
|
"pp_raw",
|
||||||
|
@ -52,13 +53,19 @@ export class User {
|
||||||
public pp:number = 0;
|
public pp:number = 0;
|
||||||
|
|
||||||
// Multiplayer data
|
// Multiplayer data
|
||||||
public currentMatch:Match | null = null;
|
public match?:Match;
|
||||||
public matchSlotId:number = -1;
|
public matchSlot?:Slot;
|
||||||
public inMatch:boolean = false;
|
public get inMatch() {
|
||||||
|
return this.match instanceof Match;
|
||||||
|
}
|
||||||
|
|
||||||
// Tournament client flag
|
// Tournament client flag
|
||||||
public isTourneyUser:boolean = false;
|
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) {
|
public constructor(id:number, username:string, uuid:string, sharedContent:SharedContent) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SharedContent } from "../BanchoServer";
|
import { SharedContent } from "../interfaces/SharedContent";
|
||||||
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");
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SharedContent } from "../BanchoServer";
|
import { SharedContent } from "../interfaces/SharedContent";
|
||||||
import { User } from "../objects/User";
|
import { User } from "../objects/User";
|
||||||
const osu = require("osu-packet");
|
const osu = require("osu-packet");
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SharedContent } from "../BanchoServer";
|
import { SharedContent } from "../interfaces/SharedContent";
|
||||||
import { User } from "../objects/User";
|
import { User } from "../objects/User";
|
||||||
const osu = require("osu-packet");
|
const osu = require("osu-packet");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue