const osu = require("osu-packet"), fs = require("fs"), packetIDs = require("./packetIDs.json"), loginHandler = require("./loginHandler.js"), parseUserData = require("./util/parseUserData.js"), userManager = require("./userManager.js"), User = require("./User.js"), bakedResponses = require("./bakedResponses.js"), Streams = require("./Streams.js"); global.users = [ new User(3, "SillyBot", "SillyBot", new Date().getTime()) ]; // Start a loop that gets new data for users from the database for use on the user panel // TODO: Some way of informing bancho that a user has set a score so details can be pulled down quickly // Possible solution, TCP socket between the score submit server and bancho? redis? (score submit is on a different server, redis probably wouldn't work) setInterval(() => { for (let i = 0; i < global.users.length; i++) { const User = global.users[i]; if (User.id == 3) continue; // Ignore the bot // Bot: :( User.getNewUserInformationFromDatabase(); } }, 10000); // Set the bot's position on the map global.users[0].location[0] = 50; global.users[0].location[1] = -32; // An array containing all currently active multiplayer matches global.matches = []; // An array containing the last 15 messages in chat // TODO: Bother making this global.chatHistory = []; 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 }, ]; // Create a stream for each chat channel for (let i = 0; i < global.channels.length; i++) { global.StreamsHandler.addStream(global.channels[i].channelName, false); } // Add a stream for the multiplayer lobby global.StreamsHandler.addStream("multiplayer_lobby", false); // Start stream checking interval global.StreamsHandler.streamChecker(5000); // Server stats global.usersOnline = 0; global.multiplayerMatches = [0, 0] global.httpRequestsPerLogInterval = 0; const logInterval = 10; // Secs setInterval(() => { global.usersOnline = (global.users.length - 1); global.multiplayerMatches = [global.matches.length, 0]; // TODO: Respect private matches fs.appendFile( "server-stats.log", `${global.usersOnline}|${global.multiplayerMatches[0]},${global.multiplayerMatches[1]}|${global.httpRequestsPerLogInterval}|${new Date().getTime()}@`, () => { } ); global.usersOnline = 0; global.multiplayerMatches = [0, 0]; global.httpRequestsPerLogInterval = 0; }, logInterval * 1000); // Include packets const ChangeAction = require("./Packets/ChangeAction.js"), SendPublicMessage = require("./Packets/SendPublicMessage.js"), Logout = require("./Packets/Logout.js"), Spectator = require("./Spectator.js"), SendPrivateMessage = require("./Packets/SendPrivateMessage.js"), Multiplayer = require("./Multiplayer.js"), ChannelPart = require("./Packets/ChannelPart.js"), UserPresenceBundle = require("./Packets/UserPresenceBundle.js"), UserPresence = require("./Packets/UserPresence.js"), UserStatsRequest = require("./Packets/UserStatsRequest.js"), MultiplayerInvite = require("./Packets/MultiplayerInvite.js"); module.exports = function(req, res) { // Add to requests for logging global.httpRequestsPerLogInterval++; // Get the client's token string and request data const requestTokenString = req.header("osu-token"), requestData = req.packet; // Server's response & new client token let responseTokenString = "", responseData = new Buffer.alloc(0); // Check if the user is logged in if (requestTokenString == null) { // Client doesn't have a token yet, let's auth them! const userData = parseUserData(requestData); global.consoleHelper.printBancho(`New client connection. [User: ${userData.username}]`); loginHandler(req, res, userData); } else { // Client has a token, let's see what they want. try { // Get the current user const userClass = userManager.getUserFromToken(requestTokenString); // Make sure the client's token isn't invalid if (userClass != null) { // Create a new osu! packet reader const osuPacketReader = new osu.Client.Reader(requestData); // Parse current bancho packet const PacketData = osuPacketReader.Parse(); // Loop through parsed packet data 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: ChangeAction(userClass, CurrentPacket.data); break; case packetIDs.client_sendPublicMessage: SendPublicMessage(CurrentPacket, userClass); break; case packetIDs.client_logout: Logout(userClass); break; case packetIDs.client_requestStatusUpdate: UserPresenceBundle(userClass); break; case packetIDs.client_pong: break; case packetIDs.client_startSpectating: Spectator.startSpectatingUser(CurrentPacket.data, userClass); break; case packetIDs.client_spectateFrames: Spectator.sendSpectatorFrames(userClass, CurrentPacket.data); break; case packetIDs.client_stopSpectating: Spectator.stopSpectatingUser(userClass); break; case packetIDs.client_sendPrivateMessage: SendPrivateMessage(CurrentPacket, userClass); break; case packetIDs.client_joinLobby: Multiplayer.userEnterLobby(userClass); break; case packetIDs.client_createMatch: Multiplayer.createMultiplayerMatch(userClass, CurrentPacket.data); break; case packetIDs.client_joinMatch: Multiplayer.joinMultiplayerMatch(userClass, CurrentPacket.data); break; case packetIDs.client_matchChangeSlot: Multiplayer.moveToSlot(userClass, CurrentPacket.data); break; case packetIDs.client_matchReady: Multiplayer.setReadyState(userClass, true); break; case packetIDs.client_matchChangeSettings: Multiplayer.updateMatch(userClass, CurrentPacket.data); break; case packetIDs.client_matchNotReady: Multiplayer.setReadyState(userClass, false); break; case packetIDs.client_partMatch: Multiplayer.leaveMatch(userClass); break; case packetIDs.client_matchLock: // Also handles user kick Multiplayer.kickPlayer(userClass, CurrentPacket.data); break; case packetIDs.client_matchNoBeatmap: Multiplayer.missingBeatmap(userClass, true); break; case packetIDs.client_matchHasBeatmap: Multiplayer.missingBeatmap(userClass, false); break; case packetIDs.client_matchTransferHost: Multiplayer.transferHost(userClass, CurrentPacket.data); break; case packetIDs.client_matchChangeMods: Multiplayer.updateMods(userClass, CurrentPacket.data); break; case packetIDs.client_matchStart: Multiplayer.startMatch(userClass); break; case packetIDs.client_matchLoadComplete: Multiplayer.setPlayerLoaded(userClass); break; case packetIDs.client_matchComplete: Multiplayer.onPlayerFinishMatch(userClass); break; case packetIDs.client_matchScoreUpdate: Multiplayer.updatePlayerScore(userClass, CurrentPacket.data); break; case packetIDs.client_matchFailed: break; case packetIDs.client_channelJoin: // TODO: Implement user channel joining // Auto channel joining is already complete break; case packetIDs.client_channelPart: ChannelPart(userClass, CurrentPacket.data); break; case packetIDs.client_userStatsRequest: UserStatsRequest(userClass, CurrentPacket.data); break; case packetIDs.client_invite: MultiplayerInvite(userClass, CurrentPacket.data); break; case packetIDs.client_userPresenceRequest: UserPresence(userClass, userClass.id); break; default: // Ignore client_beatmapInfoRequest and client_receiveUpdates if (CurrentPacket.id == 68 || CurrentPacket.id == 79) break; // Print out unimplemented packet console.dir(CurrentPacket); break; } // Put current user queue into response data responseData = Buffer.concat([responseData, userClass.queue], responseData.length + userClass.queue.length); userClass.clearQueue(); } } else { // User's token is invlid, force a reconnect global.consoleHelper.printBancho(`Forced client re-login (Token is invalid)`); responseData = bakedResponses("reconnect"); } } catch (e) { console.error(e); } finally { // Send the prepared packet to the client res.writeHead(200, { "cho-protocol": 19, "Connection": "keep-alive", "Keep-Alive": "timeout=5, max=100", "Content-Type": "text/html; charset=UTF-8" }); res.end(responseData); } } };