2022-11-20 23:37:39 +00:00
import { Channel } from "./Channel" ;
2022-11-23 00:48:28 +00:00
import { SharedContent } from "../interfaces/SharedContent" ;
2022-11-20 23:37:39 +00:00
import { DataStream } from "./DataStream" ;
import { Slot } from "./Slot" ;
import { User } from "./User" ;
import { StatusUpdate } from "../packets/StatusUpdate" ;
2022-11-23 00:48:28 +00:00
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" ;
2022-11-20 23:37:39 +00:00
2022-11-19 01:06:03 +00:00
const osu = require ( "osu-packet" ) ;
export class Match {
2022-11-20 23:37:39 +00:00
// osu! Data
public matchId :number = - 1 ;
2022-11-19 01:06:03 +00:00
public inProgress :boolean = false ;
2022-11-20 23:37:39 +00:00
public matchType :number = 0 ;
public activeMods :number = 0 ;
public gameName :string = "" ;
2022-11-21 23:26:20 +00:00
public gamePassword? :string ;
2022-11-20 23:37:39 +00:00
public beatmapName :string = '' ;
public beatmapId :number = 0 ;
public beatmapChecksum :string = '' ;
public slots :Array < Slot > = new Array < Slot > ( ) ;
2022-11-21 23:26:20 +00:00
public host :User ;
2022-11-19 01:06:03 +00:00
public playMode :number = 0 ;
public matchScoringType :number = 0 ;
public matchTeamType :number = 0 ;
public specialModes :number = 0 ;
2022-11-20 23:37:39 +00:00
public seed :number = 0 ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
// Binato data
public roundId :number = 0 ;
public matchStartCountdownActive :boolean = false ;
public matchStream :DataStream ;
public matchChatChannel :Channel ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
public matchLoadSlots? :Array < MatchStartSkipData > ;
public matchSkippedSlots? :Array < MatchStartSkipData > ;
public playerScores? :Array < PlayerScore > ;
2022-11-21 23:26:20 +00:00
private cachedMatchJSON :MatchData ;
private readonly sharedContent :SharedContent ;
2022-11-20 23:37:39 +00:00
private constructor ( matchData :MatchData , sharedContent :SharedContent ) {
2022-11-21 23:26:20 +00:00
this . sharedContent = sharedContent ;
2022-11-20 23:37:39 +00:00
this . matchId = matchData . matchId ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . inProgress = matchData . inProgress ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . matchType = matchData . matchType ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . activeMods = matchData . activeMods ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . gameName = matchData . gameName ;
if ( matchData . gamePassword == '' ) matchData . gamePassword == null ;
this . gamePassword = matchData . gamePassword ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . beatmapName = matchData . beatmapName ;
this . beatmapId = matchData . beatmapId ;
this . beatmapChecksum = matchData . beatmapChecksum ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
for ( let i = 0 ; i < matchData . slots . length ; i ++ ) {
const slot = matchData . slots [ i ] ;
2022-11-21 23:26:20 +00:00
if ( slot . playerId === - 1 ) {
2022-11-23 00:48:28 +00:00
this . slots . push ( new Slot ( i , slot . status , slot . team , undefined , slot . mods ) ) ;
2022-11-21 23:26:20 +00:00
} else {
2022-11-23 00:48:28 +00:00
this . slots . push ( new Slot ( i , slot . status , slot . team , sharedContent . users . getById ( slot . playerId ) , slot . mods ) ) ;
2022-11-21 23:26:20 +00:00
}
2022-11-19 01:06:03 +00:00
}
2022-11-21 23:26:20 +00:00
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 ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . playMode = matchData . playMode ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . matchScoringType = matchData . matchScoringType ;
this . matchTeamType = matchData . matchTeamType ;
this . specialModes = matchData . specialModes ;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
this . seed = matchData . seed ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . matchStream = sharedContent . streams . CreateStream ( ` multiplayer:match_ ${ this . matchId } ` , false ) ;
2022-11-20 23:37:39 +00:00
this . matchChatChannel = sharedContent . chatManager . AddSpecialChatChannel ( "multiplayer" , ` mp_ ${ this . matchId } ` ) ;
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
this . cachedMatchJSON = matchData ;
2022-11-20 23:37:39 +00:00
//this.playerScores = null;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
//this.multiplayerExtras = null;
2022-11-19 01:06:03 +00:00
2022-11-20 23:37:39 +00:00
//this.isTourneyMatch = false;
//this.tourneyClientUsers = [];
2022-11-19 01:06:03 +00:00
}
2022-11-20 23:37:39 +00:00
public static createMatch ( matchHost :User , matchData :MatchData , sharedContent :SharedContent ) : Promise < Match > {
return new Promise < Match > ( async ( resolve , reject ) = > {
try {
matchData . matchId = ( await sharedContent . database . query (
"INSERT INTO mp_matches (id, name, open_time, close_time, seed) VALUES (NULL, ?, UNIX_TIMESTAMP(), NULL, ?) RETURNING id;" ,
[ matchData . gameName , matchData . seed ]
) ) [ 0 ] [ "id" ] ;
const matchInstance = new Match ( matchData , sharedContent ) ;
// Update the status of the current user
StatusUpdate ( matchHost , matchHost . id ) ;
const osuPacketWriter = new osu . Bancho . Writer ;
2022-11-21 23:26:20 +00:00
osuPacketWriter . MatchNew ( matchInstance . generateMatchJSON ( ) ) ;
2022-11-20 23:37:39 +00:00
matchHost . addActionToQueue ( osuPacketWriter . toBuffer ) ;
2022-11-23 00:48:28 +00:00
sharedContent . mutiplayerManager . UpdateLobbyListing ( ) ;
2022-11-20 23:37:39 +00:00
resolve ( matchInstance ) ;
} catch ( e ) {
reject ( e ) ;
}
2022-11-19 01:06:03 +00:00
} ) ;
}
2022-11-21 23:26:20 +00:00
// Convert class data back to a format that osu-packet can understand
2022-11-23 00:48:28 +00:00
public generateMatchJSON ( ) : MatchData {
2022-11-21 23:26:20 +00:00
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 ;
}
}
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
return this . cachedMatchJSON ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
public leaveMatch ( user :User ) {
2022-11-19 01:06:03 +00:00
// Make sure this leave call is valid
2022-11-23 00:48:28 +00:00
if ( ! user . inMatch || user . matchSlot === undefined ) {
return ;
}
2022-11-19 01:06:03 +00:00
// Set the slot's status to avaliable
2022-11-23 00:48:28 +00:00
user . matchSlot . status = SlotStatus . Empty ;
user . matchSlot . team = 0 ;
user . matchSlot . player = undefined ;
user . matchSlot . mods = 0 ;
2022-11-19 01:06:03 +00:00
// Remove the leaving user from the match's stream
2022-11-23 00:48:28 +00:00
this . matchStream . RemoveUser ( user ) ;
this . matchChatChannel . Leave ( user ) ;
2022-11-19 01:06:03 +00:00
// Send this after removing the user from match streams to avoid a leave notification for self
this . sendMatchUpdate ( ) ;
2022-11-23 00:48:28 +00:00
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
public async updateMatch ( user :User , matchData :MatchData ) {
2022-11-19 01:06:03 +00:00
// Update match with new data
2022-11-21 23:26:20 +00:00
this . inProgress = matchData . inProgress ;
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
this . matchType = matchData . matchType ;
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
this . activeMods = matchData . activeMods ;
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
const gameNameChanged = this . gameName !== matchData . gameName ;
this . gameName = matchData . gameName ;
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
if ( matchData . gamePassword === "" ) {
this . gamePassword = undefined ;
} else {
this . gamePassword = this . cachedMatchJSON . gamePassword = matchData . gamePassword ;
}
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
this . beatmapName = this . cachedMatchJSON . beatmapName = matchData . beatmapName ;
this . beatmapId = this . cachedMatchJSON . beatmapId = matchData . beatmapId ;
this . beatmapChecksum = this . cachedMatchJSON . beatmapChecksum = matchData . beatmapChecksum ;
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
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
throw "Host User of match was undefined" ;
}
this . host = hostUser ;
this . cachedMatchJSON . host = this . host . id ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . playMode = this . cachedMatchJSON . playMode = matchData . playMode ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . matchScoringType = this . cachedMatchJSON . matchScoringType = matchData . matchScoringType ;
this . matchTeamType = this . cachedMatchJSON . matchTeamType = matchData . matchTeamType ;
this . specialModes = this . cachedMatchJSON . specialModes = matchData . specialModes ;
2022-11-19 01:06:03 +00:00
2022-11-21 23:26:20 +00:00
const gameSeedChanged = this . seed !== matchData . seed ;
2022-11-23 00:48:28 +00:00
this . seed = this . cachedMatchJSON . seed = matchData . seed ;
2022-11-19 01:06:03 +00:00
if ( gameNameChanged || gameSeedChanged ) {
const queryData = [ ] ;
if ( gameNameChanged ) {
2022-11-21 23:26:20 +00:00
queryData . push ( matchData . gameName ) ;
2022-11-19 01:06:03 +00:00
}
if ( gameSeedChanged ) {
2022-11-21 23:26:20 +00:00
queryData . push ( matchData . seed ) ;
2022-11-19 01:06:03 +00:00
}
queryData . push ( this . matchId ) ;
2022-11-21 23:26:20 +00:00
await this . sharedContent . database . query ( ` UPDATE mp_matches SET ${ gameNameChanged ? ` name = ? ${ gameSeedChanged ? ", " : "" } ` : "" } ${ gameSeedChanged ? ` seed = ? ` : "" } WHERE id = ? ` , queryData ) ;
2022-11-19 01:06:03 +00:00
}
this . sendMatchUpdate ( ) ;
// Update the match listing in the lobby to reflect these changes
2022-11-23 00:48:28 +00:00
this . sharedContent . mutiplayerManager . UpdateLobbyListing ( ) ;
2022-11-19 01:06:03 +00:00
}
2022-11-21 23:26:20 +00:00
public sendMatchUpdate() {
2022-11-19 01:06:03 +00:00
const osuPacketWriter = new osu . Bancho . Writer ;
2022-11-21 23:26:20 +00:00
osuPacketWriter . MatchUpdate ( this . generateMatchJSON ( ) ) ;
2022-11-19 01:06:03 +00:00
// Update all users in the match with new match information
2022-11-21 23:26:20 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
public moveToSlot ( user :User , slotToMoveTo :number ) {
if ( slotToMoveTo < 0 || slotToMoveTo >= this . slots . length ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
const newSlot = this . slots [ slotToMoveTo ] ;
if ( newSlot . status === SlotStatus . Locked || ! ( user . matchSlot instanceof Slot ) ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
user . matchSlot = newSlot . transferFrom ( user . matchSlot ) ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
2022-11-23 00:48:28 +00:00
this . sharedContent . mutiplayerManager . UpdateLobbyListing ( ) ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
public changeTeam ( user :User ) {
if ( ! ( user . matchSlot instanceof Slot ) ) {
return ;
}
user . matchSlot . team = user . matchSlot . team === Team . Red ? Team.Blue : Team.Red ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
}
2022-11-23 00:48:28 +00:00
public setStateReady ( user :User ) {
if ( ! ( user . matchSlot instanceof Slot ) ) {
return ;
}
user . matchSlot . status = SlotStatus . Ready ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
}
2022-11-23 00:48:28 +00:00
public setStateNotReady ( user :User ) {
if ( ! ( user . matchSlot instanceof Slot ) ) {
return ;
}
user . matchSlot . status = SlotStatus . NotReady ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
}
2022-11-23 00:48:28 +00:00
public lockOrKick ( user :User , slotToActionOn :number ) {
if ( slotToActionOn < 0 || slotToActionOn >= 16 ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// Make sure the user attempting to kick / lock is the host of the match
if ( User . Equals ( user , this . host ) ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
const slot = this . slots [ slotToActionOn ] ;
if ( slot . player instanceof Slot ) {
// Kick
if ( User . Equals ( user , slot . player ) ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
const kickedPlayer = slot . player ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// Remove player's refs to the match & slot
kickedPlayer . match = undefined ;
kickedPlayer . matchSlot = undefined ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// Nuke all slot properties
slot . reset ( ) ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// Send update before removing the user from the stream so they know
// they got kicked
this . sendMatchUpdate ( ) ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// 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 ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . sendMatchUpdate ( ) ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
this . sharedContent . mutiplayerManager . UpdateLobbyListing ( ) ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
public missingBeatmap ( user :User ) {
const slot = user . matchSlot ;
if ( ! ( slot instanceof Slot ) ) {
return ;
}
slot . status = SlotStatus . MissingBeatmap ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
}
2022-11-23 00:48:28 +00:00
public notMissingBeatmap ( user :User ) {
const slot = user . matchSlot ;
if ( ! ( slot instanceof Slot ) ) {
return ;
}
slot . status = SlotStatus . NotReady ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
}
2022-11-23 00:48:28 +00:00
public matchSkip ( user :User ) {
if ( this . matchSkippedSlots === undefined ) {
this . matchSkippedSlots = new Array < MatchStartSkipData > ( ) ;
2022-11-19 01:06:03 +00:00
for ( let slot of this . slots ) {
// Make sure the slot has a user in it
2022-11-23 00:48:28 +00:00
if ( slot . player === undefined || slot . status === SlotStatus . Empty || slot . status === SlotStatus . Locked ) {
continue ;
}
2022-11-19 01:06:03 +00:00
// Add the slot's user to the loaded checking array
2022-11-23 00:48:28 +00:00
this . matchSkippedSlots . push ( {
playerId : slot.player?.id ,
flag : false
} ) ;
2022-11-19 01:06:03 +00:00
}
}
let allSkipped = true ;
for ( let skippedSlot of this . matchSkippedSlots ) {
// If loadslot belongs to this user then set loaded to true
2022-11-23 00:48:28 +00:00
if ( skippedSlot . playerId === user . id ) {
skippedSlot . flag = true ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
if ( skippedSlot . flag ) continue ;
2022-11-19 01:06:03 +00:00
// A user hasn't skipped
allSkipped = false ;
}
// All players have finished playing, finish the match
if ( allSkipped ) {
const osuPacketWriter = new osu . Bancho . Writer ;
2022-11-23 00:48:28 +00:00
osuPacketWriter . MatchPlayerSkipped ( user . id ) ;
2022-11-19 01:06:03 +00:00
osuPacketWriter . MatchSkip ( ) ;
2022-11-23 00:48:28 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . matchSkippedSlots = undefined ;
2022-11-19 01:06:03 +00:00
} else {
const osuPacketWriter = new osu . Bancho . Writer ;
2022-11-23 00:48:28 +00:00
osuPacketWriter . MatchPlayerSkipped ( user . id ) ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
2022-11-19 01:06:03 +00:00
}
}
2022-11-23 00:48:28 +00:00
public transferHost ( user :User , slotIDToTransferTo :number ) {
2022-11-19 01:06:03 +00:00
// Set the lobby's host to the new user
2022-11-23 00:48:28 +00:00
const newHost = this . slots [ slotIDToTransferTo ] . player ;
if ( newHost instanceof Slot ) {
this . host = newHost ;
this . cachedMatchJSON . host = this . host . id ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . sendMatchUpdate ( ) ;
}
2022-11-19 01:06:03 +00:00
}
// TODO: Fix not being able to add DT when freemod is active
2022-11-23 00:48:28 +00:00
public updateMods ( user :User , mods :Mods ) {
const slot = user . matchSlot ;
if ( ! ( slot instanceof Slot ) ) {
return ;
}
// Check if freemod is enabled or not
2022-11-19 01:06:03 +00:00
if ( this . specialModes === 1 ) {
2022-11-23 00:48:28 +00:00
slot . mods = mods ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
} else {
2022-11-23 00:48:28 +00:00
if ( ! User . Equals ( this . host , user ) ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . activeMods = mods ;
2022-11-19 01:06:03 +00:00
this . sendMatchUpdate ( ) ;
}
2022-11-23 00:48:28 +00:00
this . sharedContent . mutiplayerManager . UpdateLobbyListing ( ) ;
2022-11-19 01:06:03 +00:00
}
startMatch() {
// Make sure the match is not already in progress
// The client sometimes double fires the start packet
2022-11-23 00:48:28 +00:00
if ( this . inProgress ) {
return ;
}
2022-11-19 01:06:03 +00:00
this . inProgress = true ;
2022-11-23 00:48:28 +00:00
this . matchLoadSlots = new Array < MatchStartSkipData > ( ) ;
2022-11-19 01:06:03 +00:00
// Loop through all slots in the match
for ( let slot of this . slots ) {
// Make sure the slot has a user in it
2022-11-23 00:48:28 +00:00
if ( slot . player === undefined || slot . status === SlotStatus . Empty || slot . status === SlotStatus . Locked ) {
continue ;
}
2022-11-19 01:06:03 +00:00
// Add the slot's user to the loaded checking array
this . matchLoadSlots . push ( {
2022-11-23 00:48:28 +00:00
playerId : slot.player?.id ,
flag : false
2022-11-19 01:06:03 +00:00
} ) ;
// Set the user's status to playing
slot . status = 32 ;
}
const osuPacketWriter = new osu . Bancho . Writer ;
2022-11-23 00:48:28 +00:00
osuPacketWriter . MatchStart ( this . generateMatchJSON ( ) ) ;
2022-11-19 01:06:03 +00:00
// Inform all users in the match that it has started
2022-11-23 00:48:28 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
2022-11-19 01:06:03 +00:00
// Update all users in the match with new info
this . sendMatchUpdate ( ) ;
// Update match listing in lobby to show the game is in progress
2022-11-23 00:48:28 +00:00
this . sharedContent . mutiplayerManager . UpdateLobbyListing ( ) ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
public matchPlayerLoaded ( user :User ) {
if ( this . matchLoadSlots === undefined ) {
return ;
}
2022-11-19 01:06:03 +00:00
let allLoaded = true ;
for ( let loadedSlot of this . matchLoadSlots ) {
2022-11-23 00:48:28 +00:00
if ( loadedSlot . playerId === user . id ) {
loadedSlot . flag = true ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
if ( loadedSlot . flag ) continue ;
2022-11-19 01:06:03 +00:00
allLoaded = false ;
}
// All players have loaded the beatmap, start playing.
if ( allLoaded ) {
let osuPacketWriter = new osu . Bancho . Writer ;
osuPacketWriter . MatchAllPlayersLoaded ( ) ;
2022-11-23 00:48:28 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
2022-11-19 01:06:03 +00:00
// Blank out user loading array
2022-11-23 00:48:28 +00:00
this . matchLoadSlots = undefined ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . playerScores = new Array < PlayerScore > ( ) ;
for ( let slot of this . slots ) {
if ( slot . player === undefined || slot . status === SlotStatus . Empty || slot . status === SlotStatus . Locked ) {
continue ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . playerScores . push ( {
player : slot.player ,
slot : slot ,
score : 0 ,
isCurrentlyFailed : false ,
hasFailed : false ,
_raw : { }
} ) ;
2022-11-19 01:06:03 +00:00
}
}
}
2022-11-23 00:48:28 +00:00
public async onPlayerFinishMatch ( user :User ) {
if ( this . matchLoadSlots === undefined ) {
2022-11-19 01:06:03 +00:00
// Repopulate user loading slots again
this . matchLoadSlots = [ ] ;
for ( let slot of this . slots ) {
// Make sure the slot has a user
2022-11-23 00:48:28 +00:00
if ( slot . player === undefined || slot . status === SlotStatus . Empty || slot . status === SlotStatus . Locked ) {
continue ;
}
2022-11-19 01:06:03 +00:00
// Populate user loading slots with this user's id and load status
this . matchLoadSlots . push ( {
2022-11-23 00:48:28 +00:00
playerId : slot.player?.id ,
flag : false
2022-11-19 01:06:03 +00:00
} ) ;
}
}
let allLoaded = true ;
for ( let loadedSlot of this . matchLoadSlots ) {
2022-11-23 00:48:28 +00:00
if ( loadedSlot . playerId == user . id ) {
loadedSlot . flag = true ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
if ( loadedSlot . flag ) continue ;
2022-11-19 01:06:03 +00:00
// A user hasn't finished playing
allLoaded = false ;
}
// All players have finished playing, finish the match
if ( allLoaded ) await this . finishMatch ( ) ;
}
2022-11-23 00:48:28 +00:00
public finishMatch() {
if ( ! this . inProgress ) {
return ;
}
this . matchLoadSlots = undefined ;
2022-11-19 01:06:03 +00:00
this . inProgress = false ;
2022-11-23 00:48:28 +00:00
2022-11-19 01:06:03 +00:00
let osuPacketWriter = new osu . Bancho . Writer ;
2022-11-23 00:48:28 +00:00
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!" ;
}
2022-11-19 01:06:03 +00:00
for ( let slot of this . slots ) {
2022-11-23 00:48:28 +00:00
// For every empty / locked slot push a null to the data array
if ( slot . player === undefined || slot . status === SlotStatus . Empty || slot . status === SlotStatus . Locked ) {
2022-11-19 01:06:03 +00:00
queryData . push ( null ) ;
continue ;
}
for ( let _playerScore of this . playerScores ) {
2022-11-23 00:48:28 +00:00
if ( _playerScore . player ? . id === slot . player ? . id ) {
const score = _playerScore . _raw ;
queryData . push ( ` ${ slot . player ? . id } | ${ score . totalScore } | ${ score . maxCombo } | ${ score . count300 } | ${ score . count100 } | ${ score . count50 } | ${ score . countGeki } | ${ score . countKatu } | ${ score . countMiss } | ${ ( score . currentHp == 254 ) ? 1 : 0 } ${ ( this . specialModes === 1 ) ? ` | ${ slot . mods } ` : "" } | ${ score . usingScoreV2 ? 1 : 0 } ${ score . usingScoreV2 ? ` | ${ score . comboPortion } | ${ score . bonusPortion } ` : "" } ` ) ;
2022-11-19 01:06:03 +00:00
break ;
}
}
2022-11-23 00:48:28 +00:00
slot . status = SlotStatus . NotReady ;
2022-11-19 01:06:03 +00:00
}
osuPacketWriter . MatchComplete ( ) ;
// Inform all users in the match that it is complete
2022-11-23 00:48:28 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
2022-11-19 01:06:03 +00:00
// Update all users in the match with new info
this . sendMatchUpdate ( ) ;
2022-11-23 00:48:28 +00:00
this . sharedContent . mutiplayerManager . UpdateLobbyListing ( ) ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// TODO: Re-implement multiplayer extras
//if (this.multiplayerExtras != null) this.multiplayerExtras.onMatchFinished(JSON.parse(JSON.stringify(this.playerScores)));
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
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 ) ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . playerScores = undefined ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
// TODO: Interface type for matchScoreData
updatePlayerScore ( user :User , matchScoreData :any ) {
2022-11-19 01:06:03 +00:00
const osuPacketWriter = new osu . Bancho . Writer ;
2022-11-23 00:48:28 +00:00
if ( user . matchSlot === undefined || this . playerScores === undefined ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
matchScoreData . id = user . matchSlot ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// Update playerScores
2022-11-19 01:06:03 +00:00
for ( let playerScore of this . playerScores ) {
2022-11-23 00:48:28 +00:00
if ( playerScore . player ? . id == user . id ) {
playerScore . score = matchScoreData . totalScore ;
const isCurrentlyFailed = matchScoreData . currentHp == 254 ;
playerScore . isCurrentlyFailed = isCurrentlyFailed ;
if ( ! playerScore . hasFailed && isCurrentlyFailed ) {
playerScore . hasFailed = true ;
}
playerScore . _raw = matchScoreData ;
2022-11-19 01:06:03 +00:00
break ;
}
}
2022-11-23 00:48:28 +00:00
osuPacketWriter . MatchScoreUpdate ( matchScoreData ) ;
2022-11-19 01:06:03 +00:00
// Send the newly updated score to all users in the match
2022-11-23 00:48:28 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
2022-11-19 01:06:03 +00:00
}
2022-11-23 00:48:28 +00:00
matchFailed ( user :User ) {
2022-11-19 01:06:03 +00:00
const osuPacketWriter = new osu . Bancho . Writer ;
2022-11-23 00:48:28 +00:00
// Make sure the user is in the match in a valid slot
if ( user . matchSlot === undefined ) {
return ;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
osuPacketWriter . MatchPlayerFailed ( user . id ) ;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
this . matchStream . Send ( osuPacketWriter . toBuffer ) ;
}
}