From 210201de158acc71a0501c440c0b4991eb616bd9 Mon Sep 17 00:00:00 2001 From: tgpethan Date: Tue, 3 Nov 2020 02:08:57 +0000 Subject: [PATCH] Introduce basic bot commands and multiplayer skip --- server/BotCommandHandler.js | 144 +++++++++++++++- server/Multiplayer.js | 254 +++++++++++++++++++++++++--- server/Packets/ChangeAction.js | 6 + server/Packets/Logout.js | 24 ++- server/Packets/SendPublicMessage.js | 39 ++++- server/Packets/StatusUpdate.js | 5 +- server/User.js | 16 +- server/serverHandler.js | 24 ++- server/util/Maths.js | 15 ++ server/util/getUserById.js | 8 +- server/util/getUserByToken.js | 8 +- server/util/getUserByUsername.js | 8 +- 12 files changed, 475 insertions(+), 76 deletions(-) create mode 100644 server/util/Maths.js diff --git a/server/BotCommandHandler.js b/server/BotCommandHandler.js index db3560b..8216db5 100644 --- a/server/BotCommandHandler.js +++ b/server/BotCommandHandler.js @@ -1,13 +1,155 @@ -module.exports = function(User, Message) { +const osu = require("osu-packet"), + maths = require("./util/Maths.js"), + Multiplayer = require("./Multiplayer.js"); + +module.exports = function(User, Message, Stream, IsCalledFromMultiplayer = false) { + if (Message[0] != "!") return; const command = Message.split(" ")[0]; + const args = Message.split(" "); + + let responseMessage = ""; + + let gay = null; switch (command) { case "!help": + // This is terrible + if (args.length == 1) { + responseMessage = "Commands:" + + "\n!help - Shows this message" + + "\n!roll - Rolls a random number or a number between 0 and a given number" + + "\n - Submenus:" + + "\n mp - Shows information about all multiplayer commands" + + "\n admin - Shows information about all admin commands"; + } else { + switch (args[1]) { + case "mp": + responseMessage = "Multiplayer Commands:" + + "\n!mp start - Starts a multiplayer match with a delay" + + "\n!mp abort - Aborts the currently running multiplayer match"; + break; + case "admin": + responseMessage = "Admin Commands:" + + "\n!lock - Locks/Unlocks a channel and limits conversation to mods and above only"; + break; + + default: + break; + } + } + break; + + case "!roll": + if (args.length == 1) { + responseMessage = User.username + " rolled " + maths.randInt(0, 65535); + } else { + if (`${parseInt(args[1])}` == "NaN") responseMessage = User.username + " rolled " + maths.randInt(0, 65535); + else responseMessage = User.username + " rolled " + maths.randInt(0, parseInt(args[1])); + } break; case "!lock": + if (!Stream.includes("#")) responseMessage = "Multiplayer channels and private channels cannot be locked!"; + else { + for (let i = 0; i < global.channels.length; i++) { + // Find the channel that pertains to this stream + if (global.channels[i].channelName == Stream) { + if (global.channels[i].locked) { + global.channels[i].locked = false; + responseMessage = "Channel is now unlocked."; + } else { + global.channels[i].locked = true; + responseMessage = "Channel is now locked, chat restricted to mods and above."; + } + break; + } + } + } + break; + case "!msg": + gay = new osu.Bancho.Writer; + + gay.RTX(args[1]); + + //User.addActionToQueue(gay.toBuffer()); + global.StreamsHandler.sendToStream(Stream, gay.toBuffer, null); + break; + + case "!fuckoff": + gay = new osu.Bancho.Writer; + + gay.Ping(0); + + User.addActionToQueue(gay.toBuffer); + break; + + case "!mp": + if (!IsCalledFromMultiplayer) return; + if (args.length == 1) return; + switch (args[1]) { + case "start": + if (args.length > 3) return; + if (`${parseInt(args[2])}` != "NaN") { + let countdown = parseInt(args[2]); + let intervalRef = setInterval(() => { + let local_osuPacketWriter = new osu.Bancho.Writer; + if (countdown != 0 && countdown > 0) countdown--; + if (countdown <= 10 && countdown > 0) { + local_osuPacketWriter.SendMessage({ + sendingClient: global.users[0].username, + message: "Starting in " + countdown, + target: "#multiplayer", + senderId: global.users[0].id + }); + global.StreamsHandler.sendToStream(Stream, local_osuPacketWriter.toBuffer, null); + } else if (countdown == 0) { + local_osuPacketWriter.SendMessage({ + sendingClient: global.users[0].username, + message: "Good luck, have fun!", + target: "#multiplayer", + senderId: global.users[0].id + }); + global.StreamsHandler.sendToStream(Stream, local_osuPacketWriter.toBuffer, null); + setTimeout(() => Multiplayer.startMatch(User), 1000); + clearInterval(intervalRef); + } + }, 1000); + } else { + responseMessage = "Good luck, have fun!"; + setTimeout(() => Multiplayer.startMatch(User), 1000); + } + break; + + case "abort": + if (args.length > 2) return; + Multiplayer.finishMatch(User); + break; + + default: + break; + } break; } + + const osuPacketWriter = new osu.Bancho.Writer; + if (responseMessage != "") { + if (Stream.includes("#")) { + osuPacketWriter.SendMessage({ + sendingClient: global.users[0].username, + message: responseMessage, + target: Stream, + senderId: global.users[0].id + }); + } else { + osuPacketWriter.SendMessage({ + sendingClient: global.users[0].username, + message: responseMessage, + target: "#multiplayer", + senderId: global.users[0].id + }); + } + } + global.StreamsHandler.sendToStream(Stream, osuPacketWriter.toBuffer, null); } \ No newline at end of file diff --git a/server/Multiplayer.js b/server/Multiplayer.js index 9efd659..2b9ab05 100644 --- a/server/Multiplayer.js +++ b/server/Multiplayer.js @@ -4,30 +4,39 @@ const osu = require("osu-packet"), module.exports = { userEnterLobby:function(currentUser) { + // If the user is currently already in a match force them to leave if (currentUser.currentMatch != null) { this.leaveMatch(currentUser); currentUser.currentMatch = null; } + // Add user to the stream for the lobby global.StreamsHandler.addUserToStream("multiplayer_lobby", currentUser.id); const osuPacketWriter1 = new osu.Bancho.Writer; - let userIds = []; + let userIds = []; - for (let i = 0; i < global.users.length; i++) { - userIds.push(global.users[i].id); - } + // Add the ID of every user connected to the server to an array + for (let i = 0; i < global.users.length; i++) { + userIds.push(global.users[i].id); + } - osuPacketWriter1.UserPresenceBundle(userIds); + // Send all user ids back to the client + osuPacketWriter1.UserPresenceBundle(userIds); - global.StreamsHandler.sendToStream("multiplayer_lobby", osuPacketWriter1.toBuffer, null); + // Send user ids to all users in the lobby + global.StreamsHandler.sendToStream("multiplayer_lobby", osuPacketWriter1.toBuffer, null); + // Loop through all matches for (let i = 0; i < global.matches.length; i++) { + // Loop through all the users in this match for (let i1 = 0; i1 < global.matches[i][1].slots.length; i1++) { const slot = global.matches[i][1].slots[i1]; + // Make sure there is a player / the slot is not locked if (slot.playerId == -1 || slot.status == 2) continue; const osuPacketWriter = new osu.Bancho.Writer; + // Get user in this slot const User = getUserById(slot.playerId); // Get user score info from the database @@ -45,21 +54,26 @@ module.exports = { accuracy: userScoreDB.avg_accuracy / 100, // Scale of 0 to 1 playCount: userScoreDB.playcount, totalScore: userScoreDB.total_score, - rank: 1, + rank: User.rank, performance: userScoreDB.pp_raw }; + // Send user status back for client display osuPacketWriter.HandleOsuUpdate(UserStatusObject); + // Send this data back to every user in the lobby global.StreamsHandler.sendToStream("multiplayer_lobby", osuPacketWriter.toBuffer, null); } const osuPacketWriter = new osu.Bancho.Writer; + // List the match on the client osuPacketWriter.MatchNew(global.matches[i][1]); currentUser.addActionToQueue(osuPacketWriter.toBuffer); } const osuPacketWriter = new osu.Bancho.Writer; + + // Add the user to the #lobby channel osuPacketWriter.ChannelJoinSuccess("#lobby"); if (!global.StreamsHandler.isUserInStream("#lobby", currentUser.id)) global.StreamsHandler.addUserToStream("#lobby", currentUser.id); @@ -67,23 +81,36 @@ module.exports = { currentUser.addActionToQueue(osuPacketWriter.toBuffer); }, + userLeaveLobby:function(currentUser) { + // Remove user from the stream for the multiplayer lobby if they are a part of it + if (global.StreamsHandler.isUserInStream("multiplayer_lobby", currentUser.id)) + global.StreamsHandler.removeUserFromStream("multiplayer_lobby", currentUser.id); + }, + updateMatchListing:function() { const osuPacketWriter1 = new osu.Bancho.Writer; let userIds = []; + // Add the ID of every user connected to the server to an array for (let i = 0; i < global.users.length; i++) { userIds.push(global.users[i].id); } + // Send all user ids back to the client osuPacketWriter1.UserPresenceBundle(userIds); + // Send user ids to all users in the lobby global.StreamsHandler.sendToStream("multiplayer_lobby", osuPacketWriter1.toBuffer, null); + // List through all matches for (let i = 0; i < global.matches.length; i++) { + // List through all users in the match for (let i1 = 0; i1 < global.matches[i][1].slots.length; i1++) { const slot = global.matches[i][1].slots[i1]; + // Make sure the slot has a user in it / isn't locked if (slot.playerId == -1 || slot.status == 2) continue; const osuPacketWriter = new osu.Bancho.Writer; + // Get the user in this slot const User = getUserById(slot.playerId); // Get user score info from the database @@ -101,18 +128,22 @@ module.exports = { accuracy: userScoreDB.avg_accuracy / 100, // Scale of 0 to 1 playCount: userScoreDB.playcount, totalScore: userScoreDB.total_score, - rank: 1, + rank: User.rank, performance: userScoreDB.pp_raw }; + // Send user status back for client display osuPacketWriter.HandleOsuUpdate(UserStatusObject); + // Send this data back to every user in the lobby global.StreamsHandler.sendToStream("multiplayer_lobby", osuPacketWriter.toBuffer, null); } const osuPacketWriter = new osu.Bancho.Writer; + // List the match on the client osuPacketWriter.MatchNew(global.matches[i][1]); + // Send this data back to every user in the lobby global.StreamsHandler.sendToStream("multiplayer_lobby", osuPacketWriter.toBuffer, null); } }, @@ -120,8 +151,10 @@ module.exports = { createMultiplayerMatch:function(currentUser, data) { const osuPacketWriter = new osu.Bancho.Writer; + // If there is no password instead set the password param to null if (data.gamePassword == '') data.gamePassword == null; + // Create a match with the data given by the creating client let NewMatchObject = { matchId: global.matches.length, inProgress: false, @@ -147,6 +180,7 @@ module.exports = { s.mods = 0; } + // Update the status of the current user StatusUpdate(currentUser, currentUser.id); osuPacketWriter.MatchNew(NewMatchObject); @@ -159,6 +193,7 @@ module.exports = { this.updateMatchListing(); + // Join the user to the newly created match this.joinMultiplayerMatch(currentUser, { matchId: NewMatchObject.matchId, gamePassword: NewMatchObject.gamePassword @@ -174,9 +209,13 @@ module.exports = { const mpLobby = global.matches[data.matchId][1]; let full = true; + // Loop through all slots to find an empty one for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the slot doesn't have a player in it / the slot is locked if (slot.playerId !== -1 || slot.status === 2) continue; + + // Slot is empty and not locked, we can join the match! full = false; slot.playerId = currentUser.id; currentUser.matchSlotId = i; @@ -188,22 +227,27 @@ module.exports = { osuPacketWriter.MatchJoinSuccess(mpLobby); if (full) { + // Inform the client that they can't join the match osuPacketWriter = new osu.Bancho.Writer; osuPacketWriter.MatchJoinFail(); - } else { - global.StreamsHandler.removeUserFromStream("multiplayer_lobby", currentUser.id); } + // Set the user's current match to this match currentUser.currentMatch = data.matchId; + // Add user to the stream for the match global.StreamsHandler.addUserToStream(streamName, currentUser.id); + // Inform all users in the match that a new user has joined global.StreamsHandler.sendToStream(streamName, osuPacketWriter1.toBuffer, null); osuPacketWriter.ChannelJoinSuccess("#multiplayer"); + // Inform joining client they they have joined the match currentUser.addActionToQueue(osuPacketWriter.toBuffer); + // Update the match listing for all users in the lobby since + // A user has joined a match this.updateMatchListing(); } catch (e) { const osuPacketWriter = new osu.Bancho.Writer; @@ -217,21 +261,25 @@ module.exports = { }, setReadyState:function(currentUser, state) { + // Get the match the user is in const mpLobby = global.matches[currentUser.currentMatch][1]; const osuPacketWriter = new osu.Bancho.Writer; + // Loop though all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Check if the player in this slot is this user if (slot.playerId == currentUser.id) { + // Turn on or off the user's ready state if (state) slot.status = 8; else slot.status = 4; - console.log("e"); break; } } osuPacketWriter.MatchUpdate(mpLobby); + // Send this update to all users in the stream global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); }, @@ -241,17 +289,21 @@ module.exports = { osuPacketWriter.MatchUpdate(mpLobby); + // Update all users in the match with new match information global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); }, updateMatch:function(currentUser, data) { + // Update match with new data global.matches[currentUser.currentMatch][1] = data; const osuPacketWriter = new osu.Bancho.Writer; osuPacketWriter.MatchUpdate(global.matches[currentUser.currentMatch][1]); + // Send this new match data to all users in the match global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + // Update the match listing in the lobby to reflect these changes this.updateMatchListing(); }, @@ -260,8 +312,10 @@ module.exports = { const osuPacketWriter = new osu.Bancho.Writer; let currentUserData, slotIndex; + // Loop through all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the user in this slot is the user we want if (slot.playerId != currentUser.id) continue; currentUserData = slot; @@ -269,17 +323,21 @@ module.exports = { break; } + // Set the new slot's data to the user's old slot data mpLobby.slots[data].playerId = currentUserData.playerId; currentUser.matchSlotId = data; mpLobby.slots[data].status = currentUserData.status; + // Set the old slot's data to open mpLobby.slots[slotIndex].playerId = -1; mpLobby.slots[slotIndex].status = 1; osuPacketWriter.MatchUpdate(mpLobby); + // Send this change to all users in the match global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + // Update the match listing in the lobby to reflect this change this.updateMatchListing(); }, @@ -287,15 +345,20 @@ module.exports = { const mpLobby = global.matches[currentUser.currentMatch][1]; const osuPacketWriter = new osu.Bancho.Writer; + // Make sure the user attempting to kick / lock is the host of the match if (mpLobby.host != currentUser.id) return; + // Get the data of the slot at the index sent by the client const slot = mpLobby.slots[data]; let cachedPlayerId = slot.playerId; + // If the slot is empty lock instead of kicking if (slot.playerId === -1) { // Slot is empty, lock it if (slot.status === 1) slot.status = 2; else slot.status = 1; - } else { // Slot isn't empty kick player + } + // The slot isn't empty, prepare to kick the player + else { const kickedPlayer = getUserById(slot.playerId); kickedPlayer.matchSlotId = -1; slot.playerId = -1; @@ -304,33 +367,91 @@ module.exports = { osuPacketWriter.MatchUpdate(mpLobby); + // Inform all users in the match of the change global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + // Update the match listing in the lobby listing to reflect this change this.updateMatchListing(); if (cachedPlayerId !== null || cachedPlayerId !== -1) { + // Remove the kicked user from the match stream global.StreamsHandler.removeUserFromStream(global.matches[currentUser.currentMatch][0], cachedPlayerId); } }, + matchSkip:function(currentUser) { + const mpLobby = global.matches[currentUser.currentMatch][1]; + + if (global.matches[currentUser.currentMatch][2] == null) { + global.matches[currentUser.currentMatch][2] = []; + + const skippedSlots = global.matches[currentUser.currentMatch][2]; + + for (let i = 0; i < mpLobby.slots.length; i++) { + const slot = mpLobby.slots[i]; + // Make sure the slot has a user in it + if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + + // Add the slot's user to the loaded checking array + skippedSlots.push({playerId: slot.playerId, skipped: false}); + } + + + } + + const skippedSlots = global.matches[currentUser.currentMatch][2]; + + for (let i = 0; i < skippedSlots.length; i++) { + // If loadslot belongs to this user then set loaded to true + if (skippedSlots[i].playerId == currentUser.id) { + skippedSlots[i].skipped = true; + } + } + + let allSkipped = true; + for (let i = 0; i < skippedSlots.length; i++) { + if (skippedSlots[i].skipped) continue; + + // A user hasn't finished playing + allSkipped = false; + } + + // All players have finished playing, finish the match + if (allSkipped) { + const osuPacketWriter = new osu.Bancho.Writer; + osuPacketWriter.MatchPlayerSkipped(currentUser.id); + osuPacketWriter.MatchSkip(); + global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + + global.matches[currentUser.currentMatch][2] = null; + } else { + const osuPacketWriter = new osu.Bancho.Writer; + osuPacketWriter.MatchPlayerSkipped(currentUser.id); + + global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + } + }, + missingBeatmap:function(currentUser, state) { const mpLobby = global.matches[currentUser.currentMatch][1]; const osuPacketWriter = new osu.Bancho.Writer; + // Loop through all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the user in the slot is the user we want to update if (slot.playerId != currentUser.id) continue; - if (state) { - slot.status = 16; - } else { - slot.status = 4; - } + // If the user is missing the beatmap set the status to reflect it + if (state) slot.status = 16; + // The user is not missing the beatmap, set the status to normal + else slot.status = 4; break; } osuPacketWriter.MatchUpdate(mpLobby); + // Inform all users in the match of this change global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); }, @@ -338,98 +459,150 @@ module.exports = { const mpLobby = global.matches[currentUser.currentMatch][1]; const osuPacketWriter = new osu.Bancho.Writer; + // Get the information of the user that the host is being transfered to const newUser = getUserById(mpLobby.slots[data].playerId); + // Set the lobby's host to the new user mpLobby.host = newUser.id; osuPacketWriter.MatchUpdate(mpLobby); + // Inform all clients in the match of the change global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); }, - updateMods(currentUser, data) { // TODO: Allow freemod to work - if (global.matches[currentUser.currentMatch][1].host !== currentUser.id) return; - const osuPacketWriter = new osu.Bancho.Writer; + // TODO: Allow freemod to work + updateMods(currentUser, data) { + // Make sure the person updating mods is the host of the match + // TODO: Add a check here for is freemod is enabled + console.log(global.matches[currentUser.currentMatch][1]); + console.log(data); + if (Object.keys(global.matches[currentUser.currentMatch][1].slots[0]).includes("mods")) { + const mpLobby = global.matches[currentUser.currentMatch][1]; + const osuPacketWriter = new osu.Bancho.Writer; + for (let i = 0; i < mpLobby.slots.length; i++) { + const slot = mpLobby.slots[i]; + if (slot.playerId === currentUser.id) { + slot.mods = data; + break; + } + } - global.matches[currentUser.currentMatch][1].activeMods = data; + osuPacketWriter.MatchUpdate(global.matches[currentUser.currentMatch][1]); - osuPacketWriter.MatchUpdate(global.matches[currentUser.currentMatch][1]); + global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + } else { + if (global.matches[currentUser.currentMatch][1].host !== currentUser.id) return; + const osuPacketWriter = new osu.Bancho.Writer; - global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + // Change the matches mods to these new mods + // TODO: Do this per user if freemod is enabled + global.matches[currentUser.currentMatch][1].activeMods = data; + osuPacketWriter.MatchUpdate(global.matches[currentUser.currentMatch][1]); + + // Inform all users in the match of the change + global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + } + + // Update match listing in the lobby to reflect this change this.updateMatchListing(); }, startMatch(currentUser) { const mpLobby = global.matches[currentUser.currentMatch][1]; + // Make sure the match is not already in progress + // The client sometimes double fires the start packet if (mpLobby.inProgress) return; mpLobby.inProgress = true; + // Create array for monitoring users until they are ready to play global.matches[currentUser.currentMatch][2] = []; const loadedSlots = global.matches[currentUser.currentMatch][2]; + // Loop through all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the slot has a user in it if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + // Add the slot's user to the loaded checking array loadedSlots.push({playerId: slot.playerId, loaded: false}); } const osuPacketWriter = new osu.Bancho.Writer; + // Loop through all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the slot has a user in it if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + // Set the user's status to playing slot.status = 32; } osuPacketWriter.MatchStart(mpLobby); + // Inform all users in the match that it has started global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + // Update all users in the match with new info this.sendMatchUpdate(currentUser); + // Update match listing in lobby to show the game is in progress this.updateMatchListing(); }, setPlayerLoaded:function(currentUser) { const loadedSlots = global.matches[currentUser.currentMatch][2]; + // Loop through all user load check items for (let i = 0; i < loadedSlots.length; i++) { + // If loadslot belongs to this user then set loaded to true if (loadedSlots[i].playerId == currentUser.id) { loadedSlots[i].loaded = true; } } + // Loop through all loaded slots and check if all users are loaded let allLoaded = true; for (let i = 0; i < loadedSlots.length; i++) { if (loadedSlots[i].loaded) continue; + // A user wasn't loaded, keep waiting. allLoaded = false; + break; } + // All players have loaded the beatmap, start playing. if (allLoaded) { let osuPacketWriter = new osu.Bancho.Writer; osuPacketWriter.MatchAllPlayersLoaded(); global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + // Blank out user loading array global.matches[currentUser.currentMatch][2] = null; } }, onPlayerFinishMatch:function(currentUser) { const mpLobby = global.matches[currentUser.currentMatch][1]; + // If user loading slots do not exist if (global.matches[currentUser.currentMatch][2] == null) { global.matches[currentUser.currentMatch][2] = []; + // Repopulate user loading slots again const loadedSlots = global.matches[currentUser.currentMatch][2]; for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the slot has a user if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + // Populate user loading slots with this user's id and load status loadedSlots.push({playerId: slot.playerId, loaded: false}); } } const loadedSlots = global.matches[currentUser.currentMatch][2]; + // Loop through all loaded slots to make sure all users have finished playing for (let i = 0; i < loadedSlots.length; i++) { if (loadedSlots[i].playerId == currentUser.id) { loadedSlots[i].loaded = true; @@ -440,9 +613,11 @@ module.exports = { for (let i = 0; i < loadedSlots.length; i++) { if (loadedSlots[i].loaded) continue; + // A user hasn't finished playing allLoaded = false; } + // All players have finished playing, finish the match if (allLoaded) this.finishMatch(currentUser); }, @@ -453,31 +628,40 @@ module.exports = { mpLobby.inProgress = false; let osuPacketWriter = new osu.Bancho.Writer; + // Loop through all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the slot has a user if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue; + // Set the user's status back to normal from playing slot.status = 4; } osuPacketWriter.MatchComplete(); + // Inform all users in the match that it is complete global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); + // Update all users in the match with new info this.sendMatchUpdate(currentUser); + // Update match info in the lobby to reflect that the match has finished this.updateMatchListing(); }, updatePlayerScore:function(currentUser, data) { const osuPacketWriter = new osu.Bancho.Writer; - if (currentUser.matchSlotId == -1) return console.log("it did the big fuck"); + // Make sure the user's slot ID is not invalid + if (currentUser.matchSlotId == -1) return; + // Get the user's current slotID and append it to the givien data, just incase. data.id = currentUser.matchSlotId; osuPacketWriter.MatchScoreUpdate(data); + // Send the newly updated score to all users in the match global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); }, @@ -485,24 +669,30 @@ module.exports = { try { const mpLobby = global.matches[currentUser.currentMatch][1]; - let containsUser = false; + let userInMatch = false; + // Loop through all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Check if the user is in this slot if (slot.playerId == currentUser.id) { - containsUser = true; + userInMatch = true; break; } } // Make sure we don't run more than once - if (!containsUser) return; + // Again, client double firing packets. + if (!userInMatch) return; let osuPacketWriter = new osu.Bancho.Writer; + // Loop through all slots in the match for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Make sure the user is in this slot if (slot.playerId != currentUser.id) continue; + // Set the slot's status to avaliable slot.playerId = -1; slot.status = 1; @@ -511,28 +701,37 @@ module.exports = { osuPacketWriter.MatchUpdate(mpLobby); + // Remove the leaving user from the match's stream global.StreamsHandler.removeUserFromStream(global.matches[currentUser.currentMatch][0], currentUser.id); + // Inform all users in the match that the leaving user has left global.StreamsHandler.sendToStream(global.matches[currentUser.currentMatch][0], osuPacketWriter.toBuffer, null); osuPacketWriter = new osu.Bancho.Writer; + // Remove user from the multiplayer channel for the match osuPacketWriter.ChannelRevoked("#multiplayer"); currentUser.addActionToQueue(osuPacketWriter.toBuffer); let empty = true; + // Check if the match is empty for (let i = 0; i < mpLobby.slots.length; i++) { const slot = mpLobby.slots[i]; + // Check if the slot is avaliable if (slot.playerId === -1) continue; + // There is a user in the match empty = false; break; } + // The match is empty, proceed to remove it. if (empty) { let matchIndex; + // Loop through all matches for (let i = 0; i < global.matches.length; i++) { + // If the match matches the match the user has left if (global.matches[i][0] == global.matches[currentUser.currentMatch][0]) { matchIndex = i; break; @@ -546,6 +745,7 @@ module.exports = { global.matches.splice(matchIndex, 1); } } catch (e) { } + // Update the match listing to reflect this change (either removal or user leaving) this.updateMatchListing(); // Delay a 2nd match listing update diff --git a/server/Packets/ChangeAction.js b/server/Packets/ChangeAction.js index 5fcb6a6..a80b351 100644 --- a/server/Packets/ChangeAction.js +++ b/server/Packets/ChangeAction.js @@ -1,3 +1,9 @@ +const StatusUpdate = require("./StatusUpdate.js"); + module.exports = function(currentUser, data) { currentUser.updatePresence(data); + if (global.StreamsHandler.doesStreamExist(`sp_${currentUser.username}`)) { + const statusUpdate = StatusUpdate(currentUser, currentUser.id, false); + global.StreamsHandler.sendToStream(`sp_${currentUser.username}`, statusUpdate, null); + } } \ No newline at end of file diff --git a/server/Packets/Logout.js b/server/Packets/Logout.js index da78a5c..dd4e995 100644 --- a/server/Packets/Logout.js +++ b/server/Packets/Logout.js @@ -1,15 +1,7 @@ const osu = require("osu-packet"); module.exports = function(CurrentUser) { - const loginStartTime = new Date().getTime(); - let userCurrentIndex; - // Find the index that the user's class is at - for (let i = 0; i < global.users.length; i++) { - if (CurrentUser.uuid == global.users[i].uuid) { - userCurrentIndex = i; - break; - } - } + const logoutStartTime = new Date().getTime(); const streamList = global.StreamsHandler.getStreams(); @@ -19,17 +11,23 @@ module.exports = function(CurrentUser) { } } - // Remove that user from the list of users - global.users.splice(userCurrentIndex, 1); + // Find the index that the user's class is at and remove the object + for (let i = 0; i < global.users.length; i++) { + if (CurrentUser.uuid == global.users[i].uuid) { + // Remove that user from the list of users + global.users.splice(i, 1); + break; + } + } const osuPacketWriter = new osu.Bancho.Writer; osuPacketWriter.SendMessage({ - sendingClient: "BanchoBot", + sendingClient: global.users[0].username, message: `User ${CurrentUser.username} has logged out.`, target: "#userlog", senderId: 3 }); global.StreamsHandler.sendToStream("#userlog", osuPacketWriter.toBuffer); - global.consoleHelper.printBancho(`User logged out, took ${new Date().getTime() - loginStartTime}ms. [User: ${CurrentUser.username}]`); + global.consoleHelper.printBancho(`User logged out, took ${new Date().getTime() - logoutStartTime}ms. [User: ${CurrentUser.username}]`); } \ No newline at end of file diff --git a/server/Packets/SendPublicMessage.js b/server/Packets/SendPublicMessage.js index 73a344e..d7aca53 100644 --- a/server/Packets/SendPublicMessage.js +++ b/server/Packets/SendPublicMessage.js @@ -1,6 +1,32 @@ -const osu = require("osu-packet"); +const osu = require("osu-packet"), + botCommandHandler = require("../BotCommandHandler.js"); module.exports = function(CurrentPacket, CurrentUser) { + let isSendingChannelLocked = false; + for (let i = 0; i < global.channels.length; i++) { + if (!CurrentPacket.data.target.includes("#")) break; + if (global.channels[i].channelName == CurrentPacket.data.target) { + isSendingChannelLocked = global.channels[i].locked; + break; + } + } + + if (isSendingChannelLocked) { + if (CurrentPacket.data.message.includes("!")) { + botCommandHandler(CurrentUser, CurrentPacket.data.message, CurrentPacket.data.target); + } else { + const osuPacketWriter = new osu.Bancho.Writer; + osuPacketWriter.SendMessage({ + sendingClient: global.users[0].username, + message: "The channel you are currently trying to send to is locked, please come back later!", + target: CurrentPacket.data.target, + senderId: global.users[0].id + }); + CurrentUser.addActionToQueue(osuPacketWriter.toBuffer); + } + return; + } + const osuPacketWriter = new osu.Bancho.Writer; osuPacketWriter.SendMessage({ sendingClient: CurrentUser.username, @@ -9,12 +35,17 @@ module.exports = function(CurrentPacket, CurrentUser) { senderId: CurrentUser.id }); - if (CurrentPacket.data.target == "#multiplayer") - return global.StreamsHandler.sendToStream(global.matches[CurrentUser.currentMatch][0], osuPacketWriter.toBuffer, CurrentUser.id); + if (CurrentPacket.data.target == "#multiplayer") { + global.StreamsHandler.sendToStream(global.matches[CurrentUser.currentMatch][0], osuPacketWriter.toBuffer, CurrentUser.id); + botCommandHandler(CurrentUser, CurrentPacket.data.message, global.matches[CurrentUser.currentMatch][0], true); + return; + } // Check the stream that we're sending to even exists if (!global.StreamsHandler.doesStreamExist(CurrentPacket.data.target)) return; // Write chat message to stream asociated with chat channel - return global.StreamsHandler.sendToStream(CurrentPacket.data.target, osuPacketWriter.toBuffer, CurrentUser.id); + global.StreamsHandler.sendToStream(CurrentPacket.data.target, osuPacketWriter.toBuffer, CurrentUser.id); + botCommandHandler(CurrentUser, CurrentPacket.data.message, CurrentPacket.data.target); + return; } \ No newline at end of file diff --git a/server/Packets/StatusUpdate.js b/server/Packets/StatusUpdate.js index 06b013f..8b32f21 100644 --- a/server/Packets/StatusUpdate.js +++ b/server/Packets/StatusUpdate.js @@ -1,7 +1,7 @@ const osu = require("osu-packet"), getUserById = require("../util/getUserById.js"); -module.exports = function(currentUser, id) { +module.exports = function(currentUser, id, sendImmidiate = true) { if (id == 3) return; // Ignore Bot // Create new osu packet writer @@ -31,5 +31,6 @@ module.exports = function(currentUser, id) { osuPacketWriter.HandleOsuUpdate(UserStatusObject); // Send data to user's queue - currentUser.addActionToQueue(osuPacketWriter.toBuffer); + if (sendImmidiate) currentUser.addActionToQueue(osuPacketWriter.toBuffer); + else return osuPacketWriter.toBuffer; } \ No newline at end of file diff --git a/server/User.js b/server/User.js index 23d0d18..8be5c0f 100644 --- a/server/User.js +++ b/server/User.js @@ -1,3 +1,5 @@ +const StatusUpdate = require("./Packets/StatusUpdate.js"); + module.exports = class { constructor(id, username, uuid, connectTime) { this.id = id; @@ -51,16 +53,26 @@ module.exports = class { // Gets the user's score information from the database and caches it getNewUserInformationFromDatabase() { const userScoreDB = global.DatabaseHelper.getFromDB(`SELECT * FROM users_modes_info WHERE user_id = ${this.id} AND mode_id = ${this.playMode} LIMIT 1`); - const userRankDB = global.DatabaseHelper.getFromDB(`SELECT user_id, pp_raw, FIND_IN_SET( pp_raw, ( SELECT GROUP_CONCAT( pp_raw ORDER BY pp_raw DESC ) FROM users_modes_info WHERE mode_id = ${this.playMode} ) ) AS rank FROM users_modes_info WHERE user_id = ${this.id} AND mode_id = ${this.playMode}`); + const userRankDB = global.DatabaseHelper.getFromDB(`SELECT user_id, pp_raw, ROW_NUMBER() OVER(ORDER BY pp_raw DESC) AS rank FROM users_modes_info WHERE mode_id = ${this.playMode} ORDER BY pp_raw DESC`); if (userScoreDB == null || userRankDB == null) throw "fuck"; + let userScoreUpdate = false; + if (this.pp != userScoreDB.pp_raw) { + userScoreUpdate = true; + } + this.rankedScore = userScoreDB.ranked_score; this.totalScore = userScoreDB.total_score; this.accuracy = userScoreDB.avg_accuracy; this.playCount = userScoreDB.playcount; - this.rank = userRankDB.rank; + for (let userRank of userRankDB) + if (userRank["user_id"] == this.id) this.rank = userRank.rank; this.pp = userScoreDB.pp_raw; + + if (userScoreUpdate) { + StatusUpdate(this, this.id); + } } // Clears out the user's queue diff --git a/server/serverHandler.js b/server/serverHandler.js index 8ca2a4d..51fda55 100644 --- a/server/serverHandler.js +++ b/server/serverHandler.js @@ -45,11 +45,11 @@ global.StreamsHandler = new Streams(); // An array containing all chat channels // TODO: Send user chat channels and not have osu! crash global.channels = [ - { channelName:"#osu", channelTopic:"The main channel", channelUserCount: 0 }, - { channelName:"#userlog", channelTopic:"Log about stuff doing go on yes very", channelUserCount: 0 }, - { channelName:"#lobby", channelTopic:"Talk about multiplayer stuff", channelUserCount: 0 }, - { channelName:"#english", channelTopic:"Talk in exclusively English", channelUserCount: 0 }, - { channelName:"#japanese", channelTopic:"Talk in exclusively Japanese", channelUserCount: 0 }, + { channelName:"#osu", channelTopic:"The main channel", channelUserCount: 0, locked: false }, + { channelName:"#userlog", channelTopic:"Log about stuff doing go on yes very", channelUserCount: 0, locked: false }, + { channelName:"#lobby", channelTopic:"Talk about multiplayer stuff", channelUserCount: 0, locked: false }, + { channelName:"#english", channelTopic:"Talk in exclusively English", channelUserCount: 0, locked: false }, + { channelName:"#japanese", channelTopic:"Talk in exclusively Japanese", channelUserCount: 0, locked: false }, ]; // Create a stream for each chat channel @@ -134,9 +134,6 @@ module.exports = function(req, res) { for (let i = 0; i < PacketData.length; i++) { // Get current packet let CurrentPacket = PacketData[i]; - - // Create a new bancho packet writer per packet - const osuPacketWriter = new osu.Bancho.Writer; switch (CurrentPacket.id) { case packetIDs.client_changeAction: @@ -155,7 +152,8 @@ module.exports = function(req, res) { UserPresenceBundle(userClass); break; - case packetIDs.client_pong: + case packetIDs.client_pong: // Pretty sure this is just a client ping + // so we probably don't do anything here break; case packetIDs.client_startSpectating: @@ -178,6 +176,10 @@ module.exports = function(req, res) { Multiplayer.userEnterLobby(userClass); break; + case packetIDs.client_partLobby: + Multiplayer.userLeaveLobby(userClass); + break; + case packetIDs.client_createMatch: Multiplayer.createMultiplayerMatch(userClass, CurrentPacket.data); break; @@ -214,6 +216,10 @@ module.exports = function(req, res) { Multiplayer.missingBeatmap(userClass, true); break; + case packetIDs.client_matchSkipRequest: + Multiplayer.matchSkip(userClass); + break; + case packetIDs.client_matchHasBeatmap: Multiplayer.missingBeatmap(userClass, false); break; diff --git a/server/util/Maths.js b/server/util/Maths.js new file mode 100644 index 0000000..4692b9b --- /dev/null +++ b/server/util/Maths.js @@ -0,0 +1,15 @@ +module.exports = { + map:function(input, inputMin, inputMax, outputMin, outputMax) { + const newv = (input - inputMin) / (inputMax - inputMin) * (outputMax - outputMin) + outputMin; + if (outputMin < outputMax) return this.constrain(newv, outputMin, outputMax); + else return this.constrain(newv, outputMax, outputMin); + }, + + constrain:function(input, low, high) { + return Math.max(Math.min(input, high), low); + }, + + randInt:function(from, to) { + return Math.round(this.map(Math.random(), 0, 1, from, to)); + } +} \ No newline at end of file diff --git a/server/util/getUserById.js b/server/util/getUserById.js index 0ab43c6..20d58e9 100644 --- a/server/util/getUserById.js +++ b/server/util/getUserById.js @@ -1,10 +1,6 @@ module.exports = function(id) { - let user = null; for (let i = 0; i < global.users.length; i++) { - if (global.users[i].id == id) { - user = global.users[i]; - break; - } + if (global.users[i].id == id) + return global.users[i]; } - return user; } \ No newline at end of file diff --git a/server/util/getUserByToken.js b/server/util/getUserByToken.js index e3348ec..260b685 100644 --- a/server/util/getUserByToken.js +++ b/server/util/getUserByToken.js @@ -1,10 +1,6 @@ module.exports = function(token) { - let user = null; for (let i = 0; i < global.users.length; i++) { - if (global.users[i].uuid == token) { - user = global.users[i]; - break; - } + if (global.users[i].uuid == token) + return global.users[i]; } - return user; } \ No newline at end of file diff --git a/server/util/getUserByUsername.js b/server/util/getUserByUsername.js index 6ca0b3f..7c2feb6 100644 --- a/server/util/getUserByUsername.js +++ b/server/util/getUserByUsername.js @@ -1,10 +1,6 @@ module.exports = function(username) { - let user = null; for (let i = 0; i < global.users.length; i++) { - if (global.users[i].username == username) { - user = global.users[i]; - break; - } + if (global.users[i].username == username) + return global.users[i]; } - return user; } \ No newline at end of file