Binato/server/objects/Match.ts

695 lines
19 KiB
TypeScript
Raw Normal View History

2023-09-10 12:59:22 +01:00
import Channel from "./Channel";
import Shared from "../objects/Shared";
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";
2023-09-10 12:59:22 +01:00
import MatchData from "../interfaces/MatchData";
2022-11-23 00:48:28 +00:00
import { Team } from "../enums/Team";
2023-09-10 12:59:22 +01:00
import MatchStartSkipData from "../interfaces/MatchStartSkipData";
2022-11-23 00:48:28 +00:00
import { Mods } from "../enums/Mods";
2023-09-10 12:59:22 +01:00
import PlayerScore from "../interfaces/PlayerScore";
import MatchScoreData from "../interfaces/MatchScoreData";
import { enumHasFlag } from "../Util";
2023-09-10 12:59:22 +01:00
import osu from "../../osuTyping";
2022-11-19 01:06:03 +00:00
// Mods which need to be applied to the match during freemod.
const matchFreemodGlobalMods:Array<Mods> = [
Mods.DoubleTime, Mods.Nightcore, Mods.HalfTime
]
2023-09-10 12:59:22 +01:00
export default 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>;
2023-08-20 13:03:01 +01:00
public countdownTime:number = 0;
public countdownTimer?:NodeJS.Timeout;
2023-09-10 18:32:35 +01:00
private serialisedMatchJSON:MatchData;
2023-08-20 13:03:01 +01:00
private readonly shared:Shared;
2022-11-21 23:26:20 +00:00
2023-08-20 13:03:01 +01:00
private constructor(matchData:MatchData, shared:Shared) {
this.shared = shared;
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 {
2023-08-20 13:03:01 +01:00
this.slots.push(new Slot(i, slot.status, slot.team, shared.users.getById(slot.playerId), slot.mods));
2022-11-21 23:26:20 +00:00
}
2022-11-19 01:06:03 +00:00
}
2023-08-20 13:03:01 +01:00
const hostUser = shared.users.getById(matchData.host);
2022-11-21 23:26:20 +00:00
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
2023-08-20 13:03:01 +01:00
this.matchStream = shared.streams.CreateStream(`multiplayer:match_${this.matchId}`, false);
this.matchChatChannel = shared.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`);
2022-11-19 01:06:03 +00:00
2023-09-10 18:32:35 +01:00
this.serialisedMatchJSON = matchData;
2022-11-21 23:26:20 +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
}
2023-08-20 13:03:01 +01:00
public static createMatch(matchHost:User, matchData:MatchData, shared:Shared) : Promise<Match> {
2022-11-20 23:37:39 +00:00
return new Promise<Match>(async (resolve, reject) => {
try {
2023-08-20 13:03:01 +01:00
matchData.matchId = (await shared.database.query(
2022-11-20 23:37:39 +00:00
"INSERT INTO mp_matches (id, name, open_time, close_time, seed) VALUES (NULL, ?, UNIX_TIMESTAMP(), NULL, ?) RETURNING id;",
[matchData.gameName, matchData.seed]
))[0]["id"];
2023-08-20 13:03:01 +01:00
const matchInstance = new Match(matchData, shared);
2022-11-20 23:37:39 +00:00
// Update the status of the current user
StatusUpdate(matchHost, matchHost.id);
2023-08-20 13:03:01 +01:00
const osuPacketWriter = osu.Bancho.Writer();
2022-11-20 23:37:39 +00:00
2023-09-10 18:32:35 +01:00
osuPacketWriter.MatchNew(matchInstance.serialiseMatch());
2022-11-20 23:37:39 +00:00
matchHost.addActionToQueue(osuPacketWriter.toBuffer);
2023-08-20 13:03:01 +01:00
shared.multiplayerManager.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
2023-09-10 18:32:35 +01:00
public serialiseMatch() : MatchData {
const matchDataRef = this.serialisedMatchJSON;
matchDataRef.matchId = this.matchId;
matchDataRef.matchType = this.matchType;
matchDataRef.activeMods = this.activeMods;
matchDataRef.gameName = this.gameName;
matchDataRef.gamePassword = this.gamePassword ?? "";
matchDataRef.inProgress = this.inProgress;
matchDataRef.beatmapName = this.beatmapName;
matchDataRef.beatmapId = this.beatmapId;
matchDataRef.beatmapChecksum = this.beatmapChecksum;
matchDataRef.host = this.host.id;
matchDataRef.playMode = this.playMode;
matchDataRef.matchScoringType = this.matchScoringType;
matchDataRef.matchTeamType = this.matchTeamType;
matchDataRef.specialModes = this.specialModes;
matchDataRef.seed = this.seed;
2022-11-21 23:26:20 +00:00
for (let i = 0; i < this.slots.length; i++) {
const slot = this.slots[i];
2023-09-10 18:32:35 +01:00
const osuSlot = this.serialisedMatchJSON.slots[i];
2022-11-21 23:26:20 +00:00
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
2023-09-10 18:32:35 +01:00
return this.serialisedMatchJSON;
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
2023-08-20 13:03:01 +01:00
// Send this after removing the user from match streams to avoid a leave notification for self?
2022-11-19 01:06:03 +00:00
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 {
2023-09-10 18:32:35 +01:00
this.gamePassword = matchData.gamePassword;
2022-11-21 23:26:20 +00:00
}
2022-11-19 01:06:03 +00:00
2023-09-10 18:32:35 +01:00
this.beatmapName = matchData.beatmapName;
this.beatmapId = matchData.beatmapId;
this.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) {
2023-08-20 13:03:01 +01:00
const hostUser = this.shared.users.getById(matchData.host);
2022-11-21 23:26:20 +00:00
if (hostUser === undefined) {
// NOTE: This should never be possible to hit
throw "Host User of match was undefined";
}
this.host = hostUser;
}
2022-11-19 01:06:03 +00:00
2023-09-10 18:32:35 +01:00
this.playMode = matchData.playMode;
2022-11-19 01:06:03 +00:00
2023-09-10 18:32:35 +01:00
this.matchScoringType = matchData.matchScoringType;
this.matchTeamType = matchData.matchTeamType;
this.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;
2023-09-10 18:32:35 +01:00
this.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);
2023-10-04 15:10:38 +01:00
await this.shared.database.execute(
2023-09-10 18:32:35 +01:00
`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
2023-08-20 13:03:01 +01:00
this.shared.multiplayerManager.UpdateLobbyListing();
2022-11-19 01:06:03 +00:00
}
2022-11-21 23:26:20 +00:00
public sendMatchUpdate() {
2023-08-20 13:03:01 +01:00
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
2023-09-10 18:32:35 +01:00
osuPacketWriter.MatchUpdate(this.serialiseMatch());
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();
2023-08-20 13:03:01 +01:00
this.shared.multiplayerManager.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
2023-09-10 18:32:35 +01:00
if (!User.Equals(user, this.host)) {
2022-11-23 00:48:28 +00:00
return;
}
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
const slot = this.slots[slotToActionOn];
2023-09-10 18:32:35 +01:00
if (slot.player instanceof User) { // Kick
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
2023-09-10 18:32:35 +01:00
// Kick player
this.shared.multiplayerManager.LeaveMatch(kickedPlayer);
2022-11-19 01:06:03 +00:00
2023-09-10 18:32:35 +01:00
this.sendMatchUpdate();
} else { // Lock / Unlock
2022-11-23 00:48:28 +00:00
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
2023-08-20 13:03:01 +01:00
this.shared.multiplayerManager.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
2023-09-10 18:32:35 +01:00
for (const slot of this.slots) {
2022-11-19 01:06:03 +00:00
// 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;
2023-09-10 18:32:35 +01:00
for (const skippedSlot of this.matchSkippedSlots) {
2022-11-19 01:06:03 +00:00
// 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;
}
2023-10-04 12:28:47 +01:00
const slotId = user.matchSlot?.slotId ?? Number.MIN_VALUE;
if (slotId === Number.MIN_VALUE) {
return;
}
2022-11-19 01:06:03 +00:00
// All players have finished playing, finish the match
if (allSkipped) {
2023-08-20 13:03:01 +01:00
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
2023-10-04 12:28:47 +01:00
osuPacketWriter.MatchPlayerSkipped(slotId);
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 {
2023-08-20 13:03:01 +01:00
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
2023-10-04 12:28:47 +01:00
osuPacketWriter.MatchPlayerSkipped(slotId);
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;
2023-09-10 18:32:35 +01:00
if (newHost instanceof User) {
2022-11-23 00:48:28 +00:00
this.host = newHost;
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
// Extra check for host during freemod
if (User.Equals(this.host, user)) {
let generatedMatchModList = 0;
for (const mod of matchFreemodGlobalMods) {
if (enumHasFlag(slot.mods, mod)) {
slot.mods -= mod;
generatedMatchModList += mod;
}
}
this.activeMods = generatedMatchModList;
}
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();
}
2023-08-20 13:03:01 +01:00
this.shared.multiplayerManager.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
2023-09-10 18:32:35 +01:00
for (const slot of this.slots) {
2022-11-19 01:06:03 +00:00
// 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;
}
2023-08-20 13:03:01 +01:00
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
2023-09-10 18:32:35 +01:00
osuPacketWriter.MatchStart(this.serialiseMatch());
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
2023-08-20 13:03:01 +01:00
this.shared.multiplayerManager.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;
2023-09-10 18:32:35 +01:00
for (const 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) {
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
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>();
2023-09-10 18:32:35 +01:00
for (const slot of this.slots) {
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
2022-11-23 00:48:28 +00:00
this.playerScores.push({
player: slot.player,
slot: slot,
score: 0,
isCurrentlyFailed: false,
hasFailed: false,
2022-11-27 15:59:43 +00:00
_raw: undefined,
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 onPlayerFinishMatch(user:User) {
if (this.matchLoadSlots === undefined) {
2022-11-19 01:06:03 +00:00
// Repopulate user loading slots again
this.matchLoadSlots = [];
2023-09-10 18:32:35 +01:00
for (const slot of this.slots) {
2022-11-19 01:06:03 +00:00
// 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;
2023-09-10 18:32:35 +01:00
for (const 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
2022-11-27 23:48:43 +00:00
if (allLoaded) {
await this.finishMatch();
}
2022-11-19 01:06:03 +00:00
}
2022-11-27 23:48:43 +00:00
public async finishMatch() {
2022-11-23 00:48:28 +00:00
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
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
2023-10-04 12:28:47 +01:00
const queryData:Array<string | number | null> = [
2022-11-23 00:48:28 +00:00
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
2023-09-10 18:32:35 +01:00
for (const 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;
}
2023-09-10 18:32:35 +01:00
for (const _playerScore of this.playerScores) {
2022-11-27 15:59:43 +00:00
if (_playerScore.player?.id === slot.player?.id && _playerScore._raw !== undefined) {
2022-11-23 00:48:28 +00:00
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
}
2023-10-04 15:10:38 +01:00
await this.shared.database.execute("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-27 23:48:43 +00:00
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();
2023-08-20 13:03:01 +01:00
this.shared.multiplayerManager.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.playerScores = undefined;
2022-11-19 01:06:03 +00:00
}
2022-11-27 15:59:43 +00:00
updatePlayerScore(user:User, matchScoreData:MatchScoreData) {
2023-08-20 13:03:01 +01:00
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
2022-11-27 15:59:43 +00:00
if (user.matchSlot === undefined || user.matchSlot.player === undefined || this.playerScores === undefined) {
2022-11-23 00:48:28 +00:00
return;
}
2022-11-19 01:06:03 +00:00
matchScoreData.id = user.matchSlot.slotId;
2022-11-19 01:06:03 +00:00
2022-11-23 00:48:28 +00:00
// Update playerScores
2023-09-10 18:32:35 +01:00
for (const playerScore of this.playerScores) {
if (playerScore.player?.id === user.id) {
2022-11-23 00:48:28 +00:00
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) {
2023-08-20 13:03:01 +01:00
const osuPacketWriter = osu.Bancho.Writer();
2022-11-19 01:06:03 +00:00
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);
}
}