Initial Future Commit (typescript)
This commit is contained in:
parent
61e7476600
commit
4ebf9ee0e6
58 changed files with 1879 additions and 3918 deletions
79
Binato.js
79
Binato.js
|
@ -1,79 +0,0 @@
|
|||
console.clear();
|
||||
|
||||
// Globals
|
||||
global.protocolVersion = 19;
|
||||
|
||||
const app = require("express")(),
|
||||
consoleHelper = require("./consoleHelper.js"),
|
||||
prometheusApp = require("express")(),
|
||||
fs = require("fs"),
|
||||
serverHandler = require("./server/serverHandler.js"),
|
||||
config = require("./config.json");
|
||||
|
||||
if (config.prometheus.enabled) {
|
||||
// We only need to require this if prom metrics are on.
|
||||
const prom = require("prom-client");
|
||||
|
||||
const register = new prom.Registry();
|
||||
|
||||
register.setDefaultLabels({ app: "nodejs_binato" });
|
||||
|
||||
prom.collectDefaultMetrics({ register });
|
||||
|
||||
prometheusApp.get("*", async (req, res) => {
|
||||
if (req.url.split("?")[0] != "/metrics") return res.status(404).end("");
|
||||
|
||||
res.end(await register.metrics());
|
||||
});
|
||||
|
||||
prometheusApp.listen(config.prometheus.port, () => consoleHelper.printBancho(`Prometheus metrics listening at port ${config.prometheus.port}`));
|
||||
} else consoleHelper.printWarn("Prometheus is disabled!");
|
||||
|
||||
if (config.express.compression) {
|
||||
app.use(require("compression")());
|
||||
consoleHelper.printBancho("Compression is enabled.");
|
||||
} else consoleHelper.printWarn("Compression is disabled!");
|
||||
|
||||
app.use((req, res) => {
|
||||
req.packet = Buffer.alloc(0);
|
||||
req.on("data", (chunk) => req.packet = Buffer.concat([req.packet, chunk], req.packet.length + chunk.length));
|
||||
req.on("end", () => {
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
if (req.url == "/" || req.url == "/index.html" || req.url == "/index") {
|
||||
res.sendFile(`${__dirname}/web/serverPage.html`);
|
||||
} else if (req.url == "/chat") {
|
||||
fs.readFile("./web/chatPageTemplate.html", (err, data) => {
|
||||
if (err) throw err;
|
||||
|
||||
let lines = "", flip = false;
|
||||
const limit = global.chatHistory.length < 10 ? 10 : global.chatHistory.length;
|
||||
for (let i = global.chatHistory.length - 10; i < limit; i++) {
|
||||
if (i < 0) i = 0;
|
||||
lines += `<div class="line line${flip ? 1 : 0}">${global.chatHistory[i] == null ? "<hidden>blank</hidden>" : global.chatHistory[i]}</div>`
|
||||
flip = !flip;
|
||||
}
|
||||
|
||||
res.send(data.toString().replace("|content|", lines));
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "POST":
|
||||
// Make sure this address should respond to bancho requests
|
||||
// Bancho addresses: c, c1, c2, c3, c4, c5, c6, ce
|
||||
// Just looking for the first character being "c" *should* be enough
|
||||
if (req.headers["host"].split(".")[0][0] == "c")
|
||||
serverHandler(req, res);
|
||||
else
|
||||
res.status(400).send("400 | Bad Request!<br>Binato only accepts POST requests on Bancho subdomains.<hr>Binato");
|
||||
break;
|
||||
|
||||
default:
|
||||
res.status(405).send("405 | Method not allowed!<hr>Binato");
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(config.express.port, () => consoleHelper.printBancho(`Binato is up! Listening at port ${config.express.port}`));
|
74
Binato.ts
Normal file
74
Binato.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { Application } from "express";
|
||||
import compression from "compression";
|
||||
import { ConsoleHelper } from "./ConsoleHelper";
|
||||
import express from "express";
|
||||
import { readFile } from "fs";
|
||||
import { Registry, collectDefaultMetrics } from "prom-client";
|
||||
|
||||
const binatoApp:Application = express();
|
||||
const config = require("./config.json");
|
||||
|
||||
if (config["prometheus"]["enabled"]) {
|
||||
const register:Registry = new Registry();
|
||||
register.setDefaultLabels({ app: "nodejs_binato" });
|
||||
|
||||
collectDefaultMetrics({ register });
|
||||
|
||||
const prometheusApp:Application = express();
|
||||
prometheusApp.get("/metrics", async (req, res) => {
|
||||
res.end(await register.metrics());
|
||||
});
|
||||
|
||||
prometheusApp.listen(config["prometheus"]["port"], () => ConsoleHelper.printBancho(`Prometheus metrics listening at port ${config["prometheus"]["port"]}`));
|
||||
} else {
|
||||
ConsoleHelper.printWarn("Prometheus is disabled!");
|
||||
}
|
||||
|
||||
if (config["express"]["compression"]) {
|
||||
binatoApp.use(compression());
|
||||
ConsoleHelper.printBancho("Compression is enabled");
|
||||
} else {
|
||||
ConsoleHelper.printWarn("Compression is disabled");
|
||||
}
|
||||
|
||||
binatoApp.use((req, res) => {
|
||||
let packet:Buffer = Buffer.alloc(0);
|
||||
req.on("data", (chunk:Buffer) => packet = Buffer.concat([packet, chunk], packet.length + chunk.length));
|
||||
req.on("end", () => {
|
||||
switch (req.method) {
|
||||
case "GET":
|
||||
if (req.url == "/" || req.url == "/index.html" || req.url == "/index") {
|
||||
res.sendFile(`${__dirname}/web/serverPage.html`);
|
||||
} else if (req.url == "/chat") {
|
||||
readFile("./web/chatPageTemplate.html", (err, data) => {
|
||||
if (err) throw err;
|
||||
|
||||
let lines = "", flip = false;
|
||||
const limit = global.chatHistory.length < 10 ? 10 : global.chatHistory.length;
|
||||
for (let i = global.chatHistory.length - 10; i < limit; i++) {
|
||||
if (i < 0) i = 0;
|
||||
lines += `<div class="line line${flip ? 1 : 0}">${global.chatHistory[i] == null ? "<hidden>blank</hidden>" : global.chatHistory[i]}</div>`
|
||||
flip = !flip;
|
||||
}
|
||||
|
||||
res.send(data.toString().replace("|content|", lines));
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case "POST":
|
||||
// Make sure this address should respond to bancho requests
|
||||
// Bancho addresses: c, c1, c2, c3, c4, c5, c6, ce
|
||||
// Just looking for the first character being "c" *should* be enough
|
||||
if (req.headers["host"].split(".")[0][0] == "c")
|
||||
serverHandler(req, res);
|
||||
else
|
||||
res.status(400).send("400 | Bad Request!<br>Binato only accepts POST requests on Bancho subdomains.<hr>Binato");
|
||||
break;
|
||||
|
||||
default:
|
||||
res.status(405).send("405 | Method not allowed!<hr>Binato");
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
63
ConsoleHelper.ts
Normal file
63
ConsoleHelper.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import chalk from "chalk";
|
||||
|
||||
enum LogType {
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR
|
||||
};
|
||||
|
||||
const LogTags = {
|
||||
BANCHO: chalk.bgMagenta(chalk.black(" BCHO ")),
|
||||
WEBREQ: chalk.bgGreen(chalk.black(" WEBR ")),
|
||||
CHAT: chalk.bgCyan(chalk.black(" CHAT ")),
|
||||
WARN: chalk.bgYellow(chalk.black(" WARN ")),
|
||||
ERROR: chalk.bgRed(" ERRR "),
|
||||
REDIS: chalk.bgRed(chalk.white(" RDIS "))
|
||||
} as const;
|
||||
|
||||
function correctValue(i:number) : string {
|
||||
if (i <= 9) return `0${i}`;
|
||||
else return i.toString();
|
||||
}
|
||||
|
||||
function getTime() : string {
|
||||
const time = new Date();
|
||||
return chalk.green(`[${correctValue(time.getHours())}:${correctValue(time.getMinutes())}:${correctValue(time.getSeconds())}]`);
|
||||
}
|
||||
|
||||
function log(tag:string, log:string, logType:LogType = LogType.INFO) : void {
|
||||
switch (logType) {
|
||||
case LogType.INFO:
|
||||
return console.log(`${getTime()} ${tag} ${log}`);
|
||||
case LogType.WARN:
|
||||
return console.warn(`${getTime()} ${tag} ${log}`);
|
||||
case LogType.ERROR:
|
||||
return console.error(`${getTime()} ${tag} ${log}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsoleHelper {
|
||||
public static printWebReq(s:string) : void {
|
||||
log(LogTags.WEBREQ, s);
|
||||
}
|
||||
|
||||
public static printBancho(s:string) : void {
|
||||
log(LogTags.BANCHO, s);
|
||||
}
|
||||
|
||||
public static printRedis(s:string) : void {
|
||||
log(LogTags.REDIS, s);
|
||||
}
|
||||
|
||||
public static printChat(s:string) : void {
|
||||
log(LogTags.CHAT, s);
|
||||
}
|
||||
|
||||
public static printWarn(s:string) : void {
|
||||
log(LogTags.WARN, s);
|
||||
}
|
||||
|
||||
public static printError(s:string) : void {
|
||||
log(LogTags.ERROR, s);
|
||||
}
|
||||
}
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Holly Stubbs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
82
README.md
82
README.md
|
@ -1,82 +0,0 @@
|
|||
# Binato [![CodeFactor](https://www.codefactor.io/repository/github/tgpholly/binato/badge)](https://www.codefactor.io/repository/github/tgpholly/binato)
|
||||
An implementation of osu!bancho in Javascript
|
||||
|
||||
i'm sorry peppy
|
||||
<hr>
|
||||
|
||||
### Features:
|
||||
- Multiplayer + Invites
|
||||
- Spectator
|
||||
- Tourney Client
|
||||
- User Panel
|
||||
- Friends List
|
||||
- Chat & Channels
|
||||
- Private Messages
|
||||
- Minimum Viable Product of a bot
|
||||
- For a command list check [BotCommandHandler](https://github.com/tgpholly/Binato/blob/master/server/BotCommandHandler.js) or use !help on a live server
|
||||
|
||||
### [Planned additions](https://github.com/tgpholly/Binato/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement) | [List of currently known bugs](https://github.com/tgpethan/Binato/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
|
||||
|
||||
<hr>
|
||||
|
||||
## Setup:
|
||||
While I don't support setting this up yourself it is fairly easy to do, all that should be required is:
|
||||
- **NodeJS >= 10**
|
||||
- **MariaDB or MySQL** (MariaDB is prefered as that is what this is tested and ran against in prod)
|
||||
- Optional (Disabled via config):
|
||||
- **Redis**
|
||||
- **Prometheus**
|
||||
|
||||
Clone the repo and run `npm i` to install required packages, then copy `config.example.json` to `config.json` and edit to your liking (this is where http compression, prometheus and redis can be enabled/disabled)
|
||||
|
||||
After doing this running `node .` should start the server
|
||||
|
||||
## Reporting bugs:
|
||||
To report a bug [create a new issue](https://github.com/tgpholly/Binato/issues/new) and include information such as your OS / Distro, Node version, disabled Binato features (e.g. Prometheus, Redis, compression) and console output at the time of the bug if applicable.
|
||||
|
||||
<hr>
|
||||
|
||||
## How to connect:
|
||||
|
||||
### 2013 - Stable Fallback (2015 / 2016 ?):
|
||||
Stable fallback uses HTTP so for that you just need to direct it to the server<br>
|
||||
You can do this using the hosts file
|
||||
|
||||
Location on Linux: /etc/hosts<br>
|
||||
Location on Mac: /private/etc/hosts<br>
|
||||
Location on Windows: C:/Windows/system32/drivers/etc/hosts
|
||||
|
||||
Add an entry in the hosts file that looks like the following:
|
||||
```
|
||||
<server_ip> osu.ppy.sh c.ppy.sh c1.ppy.sh
|
||||
```
|
||||
Where <server_ip> is the IP Address of the server hosting the bancho server
|
||||
|
||||
### 2016 - Early 2021:
|
||||
Versions of osu! past Stable Fallback use HTTPS and as such you'll have to create a self signed certificate and make the server identify as ppy.sh<br>
|
||||
In 2018 there were also new subdomains added which are:
|
||||
- c2.ppy.sh
|
||||
- c3.ppy.sh
|
||||
- c4.ppy.sh
|
||||
- c5.ppy.sh
|
||||
- c6.ppy.sh
|
||||
- ce.ppy.sh
|
||||
|
||||
### Now (2022):
|
||||
There is a `-devserver` launch flag in the game which can be passed to the client to connect to a specific server. Example usage:
|
||||
```
|
||||
osu!.exe -devserver eusv.ml
|
||||
```
|
||||
You need to have your subdomains structured like osu!'s with the exception of `c*.ppy.sh` domains. There is only one that is polled for `-devserver` usage.
|
||||
|
||||
An example setup would be:
|
||||
- osu.example.com (Score submit & web stuff)
|
||||
- c.example.com (Bancho)
|
||||
- a.example.com (Profile pictures)
|
||||
<hr>
|
||||
|
||||
## Other Binato components:
|
||||
### Website:
|
||||
Binato's website is handled by [Binato-Website](https://github.com/tgpholly/Binato-Website)
|
||||
### Profile Pictures:
|
||||
Profile pictures can be handled by any standard HTTP server, there is also one I made for the task here: [Binato-ProfilePicture](https://github.com/tgpholly/Binato-ProfilePicture)
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"express": {
|
||||
"port": 5001,
|
||||
"compression": true
|
||||
},
|
||||
"prometheus": {
|
||||
"enabled": false,
|
||||
"port": 9100
|
||||
},
|
||||
"redis": {
|
||||
"enabled": false,
|
||||
"address": "127.0.0.1",
|
||||
"port": 6379,
|
||||
"password": "",
|
||||
"database": 0
|
||||
},
|
||||
"database": {
|
||||
"address": "127.0.0.1",
|
||||
"port": 3306,
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"name": "osu!",
|
||||
"pbkdf2": {
|
||||
"itterations": 1337,
|
||||
"keylength": 1337
|
||||
},
|
||||
"key": "examplekey"
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
const chalk = require("chalk");
|
||||
|
||||
const LogType = {
|
||||
INFO: 0,
|
||||
WARN: 1,
|
||||
ERROR: 2
|
||||
}
|
||||
|
||||
const LogTags = {
|
||||
BANCHO: chalk.bgMagenta(chalk.black(" BANCHO ")),
|
||||
WEBREQ: chalk.bgGreen(chalk.black(" WEBREQ ")),
|
||||
CHAT: chalk.bgCyan(chalk.black(" CHATTO ")),
|
||||
WARN: chalk.bgYellow(chalk.black(" WARNIN ")),
|
||||
ERROR: chalk.bgRed(" ERROR! "),
|
||||
REDIS: chalk.bgRed(chalk.white(" bREDIS "))
|
||||
}
|
||||
|
||||
function correctValue(i) {
|
||||
if (i <= 9) return "0"+i;
|
||||
else return i;
|
||||
}
|
||||
|
||||
function getTime() {
|
||||
const time = new Date();
|
||||
return chalk.green(`[${correctValue(time.getHours())}:${correctValue(time.getMinutes())}:${correctValue(time.getSeconds())}]`);
|
||||
}
|
||||
|
||||
function log(tag = "", log = "", logType = LogType.INFO) {
|
||||
switch (logType) {
|
||||
case LogType.INFO:
|
||||
return console.log(`${getTime()} ${tag} ${log}`);
|
||||
case LogType.WARN:
|
||||
return console.warn(`${getTime()} ${tag} ${log}`);
|
||||
case LogType.ERROR:
|
||||
return console.error(`${getTime()} ${tag} ${log}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
printWebReq:function(s) {
|
||||
log(LogTags.WEBREQ, s);
|
||||
},
|
||||
|
||||
printBancho:function(s) {
|
||||
log(LogTags.BANCHO, s);
|
||||
},
|
||||
|
||||
printRedis:function(s) {
|
||||
log(LogTags.REDIS, s);
|
||||
},
|
||||
|
||||
printChat:function(s) {
|
||||
log(LogTags.CHAT, s);
|
||||
},
|
||||
|
||||
printWarn:function(s) {
|
||||
log(LogTags.WARN, chalk.yellow(s), LogType.WARN);
|
||||
},
|
||||
|
||||
printError:function(s) {
|
||||
log(LogTags.ERROR, chalk.red(s), LogType.ERROR);
|
||||
}
|
||||
}
|
176
osu!.sql
176
osu!.sql
|
@ -1,176 +0,0 @@
|
|||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS `osu!` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
|
||||
USE `osu!`;
|
||||
|
||||
CREATE TABLE `friends` (
|
||||
`user` int(11) NOT NULL,
|
||||
`friendsWith` int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `scores` (
|
||||
`id` int(11) NOT NULL,
|
||||
`userid` int(11) NOT NULL,
|
||||
`beatmap_md5` varchar(32) NOT NULL DEFAULT '',
|
||||
`username` varchar(30) NOT NULL DEFAULT '',
|
||||
`score` bigint(20) NOT NULL,
|
||||
`max_combo` int(11) NOT NULL DEFAULT '0',
|
||||
`full_combo` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`mods` int(11) NOT NULL DEFAULT '0',
|
||||
`300_count` int(11) NOT NULL DEFAULT '0',
|
||||
`100_count` int(11) NOT NULL DEFAULT '0',
|
||||
`50_count` int(11) NOT NULL DEFAULT '0',
|
||||
`katus_count` int(11) NOT NULL DEFAULT '0',
|
||||
`gekis_count` int(11) NOT NULL DEFAULT '0',
|
||||
`misses_count` int(11) NOT NULL DEFAULT '0',
|
||||
`time` varchar(18) NOT NULL DEFAULT '',
|
||||
`play_mode` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`completed` tinyint(11) NOT NULL DEFAULT '0',
|
||||
`accuracy` float(15,12) DEFAULT NULL,
|
||||
`pp` float NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `mp_matches` (
|
||||
`id` int(10) UNSIGNED NOT NULL,
|
||||
`name` varchar(127) NOT NULL,
|
||||
`open_time` varchar(18) NOT NULL,
|
||||
`close_time` varchar(18) DEFAULT NULL,
|
||||
`seed` int(11) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `mp_match_rounds` (
|
||||
`id` int(11) NOT NULL,
|
||||
`match_id` int(11) NOT NULL,
|
||||
`round_id` int(11) NOT NULL,
|
||||
`round_mode` tinyint(4) NOT NULL,
|
||||
`match_type` tinyint(4) NOT NULL,
|
||||
`round_scoring_type` tinyint(4) NOT NULL,
|
||||
`round_team_type` tinyint(4) NOT NULL,
|
||||
`round_mods` int(11) NOT NULL,
|
||||
`beatmap_md5` varchar(127) NOT NULL,
|
||||
`freemod` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`player0` tinytext DEFAULT NULL,
|
||||
`player1` tinytext DEFAULT NULL,
|
||||
`player2` tinytext DEFAULT NULL,
|
||||
`player3` tinytext DEFAULT NULL,
|
||||
`player4` tinytext DEFAULT NULL,
|
||||
`player5` tinytext DEFAULT NULL,
|
||||
`player6` tinytext DEFAULT NULL,
|
||||
`player7` tinytext DEFAULT NULL,
|
||||
`player8` tinytext DEFAULT NULL,
|
||||
`player9` tinytext DEFAULT NULL,
|
||||
`player10` tinytext DEFAULT NULL,
|
||||
`player11` tinytext DEFAULT NULL,
|
||||
`player12` tinytext DEFAULT NULL,
|
||||
`player13` tinytext DEFAULT NULL,
|
||||
`player14` tinytext DEFAULT NULL,
|
||||
`player15` tinytext DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `users_info` (
|
||||
`id` int(11) NOT NULL,
|
||||
`username` varchar(15) NOT NULL,
|
||||
`username_safe` varchar(15) NOT NULL,
|
||||
`password_hash` text NOT NULL,
|
||||
`password_salt` text NOT NULL,
|
||||
`email` text NOT NULL,
|
||||
`country` varchar(2) NOT NULL,
|
||||
`reg_date` datetime NOT NULL,
|
||||
`last_login_date` datetime NOT NULL,
|
||||
`last_played_mode` tinyint(4) NOT NULL,
|
||||
`online_now` tinyint(1) NOT NULL,
|
||||
`tags` int(11) NOT NULL,
|
||||
`supporter` tinyint(1) NOT NULL,
|
||||
`web_session` varchar(64) NOT NULL,
|
||||
`verification_needed` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`password_change_required` tinyint(1) NOT NULL,
|
||||
`has_old_password` int(11) NOT NULL DEFAULT 0,
|
||||
`password_reset_key` text DEFAULT NULL,
|
||||
`away_message` varchar(100) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `users_modes_info` (
|
||||
`n` int(11) NOT NULL,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`mode_id` tinyint(4) NOT NULL,
|
||||
`count300` int(10) UNSIGNED NOT NULL,
|
||||
`count100` int(10) UNSIGNED NOT NULL,
|
||||
`count50` int(10) UNSIGNED NOT NULL,
|
||||
`countmiss` int(10) UNSIGNED NOT NULL,
|
||||
`playcount` int(10) UNSIGNED NOT NULL,
|
||||
`total_score` int(10) UNSIGNED NOT NULL,
|
||||
`ranked_score` int(11) UNSIGNED NOT NULL,
|
||||
`pp_rank` int(11) NOT NULL,
|
||||
`pp_raw` int(11) NOT NULL DEFAULT '1',
|
||||
`count_rank_ss` int(10) UNSIGNED NOT NULL,
|
||||
`count_rank_s` int(10) UNSIGNED NOT NULL,
|
||||
`count_rank_a` int(10) UNSIGNED NOT NULL,
|
||||
`pp_country_rank` int(11) NOT NULL,
|
||||
`playtime` bigint(255) NOT NULL DEFAULT '0',
|
||||
`avg_accuracy` float NOT NULL DEFAULT '0',
|
||||
`level` int(255) NOT NULL DEFAULT '0'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `web_info` (
|
||||
`i` int(11) NOT NULL,
|
||||
`HomepageText` varchar(255) NOT NULL DEFAULT 'A default Binato instance!'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `web_prefs` (
|
||||
`id` int(11) NOT NULL,
|
||||
`keyboard` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`mouse` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`tablet` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`touch` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`location` varchar(32) NOT NULL,
|
||||
`interests` varchar(64) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
CREATE TABLE `web_titles` (
|
||||
`id` int(11) NOT NULL,
|
||||
`title` varchar(32) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||
|
||||
|
||||
ALTER TABLE `scores`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
ALTER TABLE `users_info`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `id` (`id`);
|
||||
|
||||
ALTER TABLE `users_modes_info`
|
||||
ADD PRIMARY KEY (`n`);
|
||||
|
||||
ALTER TABLE `web_info`
|
||||
ADD PRIMARY KEY (`i`);
|
||||
|
||||
ALTER TABLE `web_prefs`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
ALTER TABLE `web_titles`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
ALTER TABLE `mp_matches`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
ALTER TABLE `mp_match_rounds`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
ALTER TABLE `mp_matches`
|
||||
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
|
||||
|
||||
ALTER TABLE `mp_match_rounds`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
|
||||
|
||||
ALTER TABLE `scores`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=0;
|
||||
|
||||
ALTER TABLE `users_info`
|
||||
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=100;
|
||||
|
||||
ALTER TABLE `users_modes_info`
|
||||
MODIFY `n` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=0;
|
||||
|
||||
INSERT INTO `web_info` (`i`, `HomepageText`) VALUES ('0', 'A default Binato instance!');
|
2119
package-lock.json
generated
2119
package-lock.json
generated
File diff suppressed because it is too large
Load diff
20
package.json
Executable file → Normal file
20
package.json
Executable file → Normal file
|
@ -2,21 +2,25 @@
|
|||
"name": "binato",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"main": "Binato.ts",
|
||||
"scripts": {
|
||||
"dev:run": "nodemon --watch './**/*.ts' Binato.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aes256": "^1.1.0",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^18.11.9",
|
||||
"chalk": "^4.1.0",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.17.1",
|
||||
"express": "^4.18.2",
|
||||
"mysql2": "^2.3.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"osu-packet": "^4.1.2",
|
||||
"prom-client": "^13.2.0",
|
||||
"redis": "^4.0.6",
|
||||
"uuid": "^8.3.2"
|
||||
"prom-client": "^14.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.20",
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
}
|
||||
|
|
105
server/serverHandler.js → server/BanchoServer.ts
Executable file → Normal file
105
server/serverHandler.js → server/BanchoServer.ts
Executable file → Normal file
|
@ -1,7 +1,8 @@
|
|||
const osu = require("osu-packet"),
|
||||
fs = require("fs"),
|
||||
consoleHelper = require("../consoleHelper.js"),
|
||||
packetIDs = require("./packetIDs.js"),
|
||||
import * as osu from "osu-packet";
|
||||
import { ConsoleHelper } from "../ConsoleHelper";
|
||||
import { Packets } from "./enums/Packets";
|
||||
|
||||
const
|
||||
loginHandler = require("./loginHandler.js"),
|
||||
parseUserData = require("./util/parseUserData.js"),
|
||||
User = require("./User.js"),
|
||||
|
@ -34,7 +35,7 @@ async function subscribeToChannel(channelName = "", callback = function(message
|
|||
await subscriptionClient.connect();
|
||||
// Subscribe to channel
|
||||
await subscriptionClient.subscribe(channelName, callback);
|
||||
consoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
||||
ConsoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
||||
}
|
||||
|
||||
// Do redis if it's enabled
|
||||
|
@ -128,25 +129,25 @@ const ChangeAction = require("./Packets/ChangeAction.js"),
|
|||
// A class for managing everything multiplayer
|
||||
global.MultiplayerManager = new MultiplayerManager();
|
||||
|
||||
module.exports = async function(req, res) {
|
||||
module.exports = async function(req, res, packet:Buffer) {
|
||||
// Get the client's token string and request data
|
||||
const requestTokenString = req.header("osu-token"),
|
||||
requestData = req.packet;
|
||||
const requestTokenString:string = req.header("osu-token"),
|
||||
requestData:Buffer = packet;
|
||||
|
||||
// Server's response
|
||||
let responseData;
|
||||
let responseData:Buffer;
|
||||
|
||||
// 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);
|
||||
consoleHelper.printBancho(`New client connection. [User: ${userData.username}]`);
|
||||
ConsoleHelper.printBancho(`New client connection. [User: ${userData.username}]`);
|
||||
await loginHandler(req, res, userData);
|
||||
} else {
|
||||
// Client has a token, let's see what they want.
|
||||
try {
|
||||
// Get the current user
|
||||
const PacketUser = getUserFromToken(requestTokenString);
|
||||
const PacketUser:User = getUserFromToken(requestTokenString);
|
||||
|
||||
// Make sure the client's token isn't invalid
|
||||
if (PacketUser != null) {
|
||||
|
@ -161,164 +162,164 @@ module.exports = async function(req, res) {
|
|||
// Go through each packet sent by the client
|
||||
for (CurrentPacket of PacketData) {
|
||||
switch (CurrentPacket.id) {
|
||||
case packetIDs.client_changeAction:
|
||||
case Packets.Client_ChangeAction:
|
||||
ChangeAction(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_sendPublicMessage:
|
||||
case Packets.Client_SendPublicMessage:
|
||||
SendPublicMessage(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_logout:
|
||||
case Packets.Client_Logout:
|
||||
await Logout(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_requestStatusUpdate:
|
||||
case Packets.Client_RequestStatusUpdate:
|
||||
UserPresenceBundle(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_startSpectating:
|
||||
case Packets.Client_StartSpectating:
|
||||
Spectator.startSpectatingUser(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_spectateFrames:
|
||||
case Packets.Client_SpectateFrames:
|
||||
Spectator.sendSpectatorFrames(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_stopSpectating:
|
||||
case Packets.Client_StopSpectating:
|
||||
Spectator.stopSpectatingUser(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_sendPrivateMessage:
|
||||
case Packets.client_sendPrivateMessage:
|
||||
SendPrivateMessage(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_joinLobby:
|
||||
case Packets.client_joinLobby:
|
||||
global.MultiplayerManager.userEnterLobby(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_partLobby:
|
||||
case Packets.client_partLobby:
|
||||
global.MultiplayerManager.userLeaveLobby(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_createMatch:
|
||||
case Packets.client_createMatch:
|
||||
await global.MultiplayerManager.createMultiplayerMatch(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_joinMatch:
|
||||
case Packets.client_joinMatch:
|
||||
global.MultiplayerManager.joinMultiplayerMatch(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchChangeSlot:
|
||||
case Packets.client_matchChangeSlot:
|
||||
PacketUser.currentMatch.moveToSlot(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchReady:
|
||||
case Packets.client_matchReady:
|
||||
PacketUser.currentMatch.setStateReady(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchChangeSettings:
|
||||
case Packets.client_matchChangeSettings:
|
||||
await PacketUser.currentMatch.updateMatch(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchNotReady:
|
||||
case Packets.client_matchNotReady:
|
||||
PacketUser.currentMatch.setStateNotReady(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_partMatch:
|
||||
case Packets.client_partMatch:
|
||||
await global.MultiplayerManager.leaveMultiplayerMatch(PacketUser);
|
||||
break;
|
||||
|
||||
// Also handles user kick if the slot has a user
|
||||
case packetIDs.client_matchLock:
|
||||
case Packets.client_matchLock:
|
||||
PacketUser.currentMatch.lockMatchSlot(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchNoBeatmap:
|
||||
case Packets.client_matchNoBeatmap:
|
||||
PacketUser.currentMatch.missingBeatmap(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchSkipRequest:
|
||||
case Packets.client_matchSkipRequest:
|
||||
PacketUser.currentMatch.matchSkip(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchHasBeatmap:
|
||||
case Packets.client_matchHasBeatmap:
|
||||
PacketUser.currentMatch.notMissingBeatmap(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchTransferHost:
|
||||
case Packets.client_matchTransferHost:
|
||||
PacketUser.currentMatch.transferHost(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchChangeMods:
|
||||
case Packets.client_matchChangeMods:
|
||||
PacketUser.currentMatch.updateMods(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchStart:
|
||||
case Packets.client_matchStart:
|
||||
PacketUser.currentMatch.startMatch();
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchLoadComplete:
|
||||
case Packets.client_matchLoadComplete:
|
||||
PacketUser.currentMatch.matchPlayerLoaded(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchComplete:
|
||||
case Packets.client_matchComplete:
|
||||
await PacketUser.currentMatch.onPlayerFinishMatch(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchScoreUpdate:
|
||||
case Packets.client_matchScoreUpdate:
|
||||
PacketUser.currentMatch.updatePlayerScore(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchFailed:
|
||||
case Packets.client_matchFailed:
|
||||
PacketUser.currentMatch.matchFailed(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_matchChangeTeam:
|
||||
case Packets.client_matchChangeTeam:
|
||||
PacketUser.currentMatch.changeTeam(PacketUser);
|
||||
break;
|
||||
|
||||
case packetIDs.client_channelJoin:
|
||||
case Packets.client_channelJoin:
|
||||
ChannelJoin(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_channelPart:
|
||||
case Packets.client_channelPart:
|
||||
ChannelPart(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_setAwayMessage:
|
||||
case Packets.client_setAwayMessage:
|
||||
SetAwayMessage(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_friendAdd:
|
||||
case Packets.client_friendAdd:
|
||||
AddFriend(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_friendRemove:
|
||||
case Packets.client_friendRemove:
|
||||
RemoveFriend(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_userStatsRequest:
|
||||
case Packets.client_userStatsRequest:
|
||||
UserStatsRequest(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_specialMatchInfoRequest:
|
||||
case Packets.client_specialMatchInfoRequest:
|
||||
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_specialJoinMatchChannel:
|
||||
case Packets.client_specialJoinMatchChannel:
|
||||
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_specialLeaveMatchChannel:
|
||||
case Packets.client_specialLeaveMatchChannel:
|
||||
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_invite:
|
||||
case Packets.client_invite:
|
||||
MultiplayerInvite(PacketUser, CurrentPacket.data);
|
||||
break;
|
||||
|
||||
case packetIDs.client_userPresenceRequest:
|
||||
case Packets.client_userPresenceRequest:
|
||||
UserPresence(PacketUser, PacketUser.id); // Can't really think of a way to generalize this?
|
||||
break;
|
||||
|
||||
|
@ -352,4 +353,4 @@ module.exports = async function(req, res) {
|
|||
res.end(responseData);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
|
@ -1,213 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
maths = require("./util/Maths.js"),
|
||||
Streams = require("./Streams.js"),
|
||||
OsuBattleRoyale = require("./MultiplayerExtras/OsuBattleRoyale.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 commandBanchoPacketWriter = null;
|
||||
|
||||
switch (command) {
|
||||
case "!help":
|
||||
// This is terrible
|
||||
if (args.length == 1) {
|
||||
responseMessage = "Commands with an * next to them have a sub help section" +
|
||||
"\n!help - Shows this message" +
|
||||
"\n!roll - Rolls a random number or a number between 0 and a given number" +
|
||||
"\n!ranking* - Sets your perfered ranking type" +
|
||||
"\n!mp* - Shows information about all multiplayer commands" +
|
||||
"\n!admin* - Shows information about all admin commands";
|
||||
} else {
|
||||
switch (args[1]) {
|
||||
case "ranking":
|
||||
responseMessage = "Ranking Commands:" +
|
||||
"\n!ranking pp - Sets your ranking type to pp" +
|
||||
"\n!ranking score - Sets your ranking type to score" +
|
||||
"\n!ranking acc - Sets your ranking type to accuracy";
|
||||
break;
|
||||
|
||||
case "mp":
|
||||
responseMessage = "Multiplayer Commands:" +
|
||||
"\n!mp start - Starts a multiplayer match with a delay" +
|
||||
"\n!mp abort - Aborts the currently running multiplayer match" +
|
||||
"\n!mp obr - Enables Battle Royale mode";
|
||||
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 "!ranking":
|
||||
if (args.length == 1) {
|
||||
responseMessage = "You need to select a ranking mode! use \"!help ranking\" to see the options.";
|
||||
} else {
|
||||
switch (args[1]) {
|
||||
case "pp":
|
||||
responseMessage = "Set ranking mode to pp";
|
||||
User.rankingMode = 0;
|
||||
User.updateUserInfo(true);
|
||||
break;
|
||||
|
||||
case "score":
|
||||
responseMessage = "Set ranking mode to score";
|
||||
User.rankingMode = 1;
|
||||
User.updateUserInfo(true);
|
||||
break;
|
||||
|
||||
case "acc":
|
||||
responseMessage = "Set ranking mode to accuracy";
|
||||
User.rankingMode = 2;
|
||||
User.updateUserInfo(true);
|
||||
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 "!mp":
|
||||
if (!IsCalledFromMultiplayer) return;
|
||||
if (User.currentMatch.matchStartCountdownActive) return;
|
||||
if (args.length == 1) return;
|
||||
switch (args[1]) {
|
||||
case "start":
|
||||
if (args.length > 3) return;
|
||||
if (!isNaN(args[2])) {
|
||||
User.currentMatch.matchStartCountdownActive = true;
|
||||
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.botUser.username,
|
||||
message: "Starting in " + countdown,
|
||||
target: "#multiplayer",
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
Streams.sendToStream(Stream, local_osuPacketWriter.toBuffer, null);
|
||||
} else if (countdown == 0) {
|
||||
local_osuPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "Good luck, have fun!",
|
||||
target: "#multiplayer",
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
Streams.sendToStream(Stream, local_osuPacketWriter.toBuffer, null);
|
||||
User.currentMatch.matchStartCountdownActive = false;
|
||||
setTimeout(() => User.currentMatch.startMatch(), 1000);
|
||||
clearInterval(intervalRef);
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
responseMessage = "Good luck, have fun!";
|
||||
setTimeout(() => User.currentMatch.startMatch(), 1000);
|
||||
}
|
||||
break;
|
||||
|
||||
case "abort":
|
||||
//if (args.length > 2) return;
|
||||
User.currentMatch.finishMatch();
|
||||
break;
|
||||
|
||||
case "obr":
|
||||
if (User.currentMatch.multiplayerExtras != null) {
|
||||
if (User.currentMatch.multiplayerExtras.name == "osu! Battle Royale") {
|
||||
commandBanchoPacketWriter = new osu.Bancho.Writer;
|
||||
commandBanchoPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "osu! Battle Royale has been disabled!",
|
||||
target: "#multiplayer",
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
User.currentMatch.multiplayerExtras = null;
|
||||
Streams.sendToStream(Stream, commandBanchoPacketWriter.toBuffer, null);
|
||||
}
|
||||
else enableOBR(User, Stream, commandBanchoPacketWriter);
|
||||
}
|
||||
else enableOBR(User, Stream, commandBanchoPacketWriter);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
if (responseMessage != "") {
|
||||
if (Stream.includes("#")) {
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: responseMessage,
|
||||
target: Stream,
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
} else {
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: responseMessage,
|
||||
target: "#multiplayer",
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
}
|
||||
}
|
||||
Streams.sendToStream(Stream, osuPacketWriter.toBuffer, null);
|
||||
}
|
||||
|
||||
function enableOBR(User, Stream, commandBanchoPacketWriter) {
|
||||
User.currentMatch.multiplayerExtras = new OsuBattleRoyale(User.currentMatch);
|
||||
commandBanchoPacketWriter = new osu.Bancho.Writer;
|
||||
commandBanchoPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "osu! Battle Royale has been enabled!",
|
||||
target: "#multiplayer",
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
commandBanchoPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "New Multiplayer Rules Added:\n - Players that are in a failed state by the end of the map get eliminated\n - The player(s) with the lowest score get eliminated",
|
||||
target: "#multiplayer",
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
Streams.sendToStream(Stream, commandBanchoPacketWriter.toBuffer, null);
|
||||
}
|
0
server/Channels.ts
Normal file
0
server/Channels.ts
Normal file
|
@ -1,79 +0,0 @@
|
|||
const mysql = require("mysql2");
|
||||
const consoleHelper = require("../consoleHelper.js");
|
||||
|
||||
module.exports = class {
|
||||
constructor(databaseAddress, databasePort = 3306, databaseUsername, databasePassword, databaseName, connectedCallback) {
|
||||
this.connectionPool = mysql.createPool({
|
||||
connectionLimit: 128,
|
||||
host: databaseAddress,
|
||||
port: databasePort,
|
||||
user: databaseUsername,
|
||||
password: databasePassword,
|
||||
database: databaseName
|
||||
});
|
||||
|
||||
const classCreationTime = Date.now();
|
||||
this.dbActive = false;
|
||||
if (connectedCallback == null) {
|
||||
this.dbActive = true;
|
||||
} else {
|
||||
const connectionCheckInterval = setInterval(() => {
|
||||
this.query("SELECT name FROM osu_info LIMIT 1")
|
||||
.then(data => {
|
||||
consoleHelper.printBancho(`Connected to database. Took ${Date.now() - classCreationTime}ms`);
|
||||
this.dbActive = true;
|
||||
clearInterval(connectionCheckInterval);
|
||||
|
||||
connectedCallback();
|
||||
})
|
||||
.catch(err => {});
|
||||
}, 167); // Roughly 6 times per sec
|
||||
}
|
||||
}
|
||||
|
||||
query(query = "", data) {
|
||||
const limited = query.includes("LIMIT 1");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.connectionPool.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
try { connection.release();}
|
||||
catch (e) {
|
||||
console.error("Failed to release mysql connection", err);
|
||||
}
|
||||
} else {
|
||||
// Use old query
|
||||
if (data == null) {
|
||||
connection.query(query, (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
connection.release();
|
||||
} else {
|
||||
dataReceived(resolve, data, limited);
|
||||
connection.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
// Use new prepared statements w/ placeholders
|
||||
else {
|
||||
connection.execute(query, data, (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
connection.release();
|
||||
} else {
|
||||
dataReceived(resolve, data, limited);
|
||||
connection.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function dataReceived(resolveCallback, data, limited = false) {
|
||||
if (limited) resolveCallback(data[0]);
|
||||
else resolveCallback(data);
|
||||
}
|
|
@ -1,143 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
MultiplayerMatch = require("../MultiplayerMatch.js"),
|
||||
getUserById = require("../util/getUserById.js"),
|
||||
Streams = require("../Streams.js");
|
||||
|
||||
function sameScoreCheck(playerScores = [{playerId:0,slotId:0,score:0,isCurrentlyFailed:false}], lowestScore = 0) {
|
||||
for (let playerScore of playerScores) {
|
||||
// All players don't have the same score
|
||||
if (playerScore.score != lowestScore || playerScore.isCurrentlyFailed)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function kickLowScorers(playerScores = [{playerId:0,slotId:0,score:0,isCurrentlyFailed:false}], MultiplayerMatch) {
|
||||
for (let playerScore of playerScores) {
|
||||
// Kick players if they have the lowest score or they are in a failed state
|
||||
if (playerScore.score == lowestScore || playerScore.isCurrentlyFailed) {
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
// Get the slot this player is in
|
||||
const slot = MultiplayerMatch.slots[playerScore.slotId];
|
||||
// Get the kicked player's user class
|
||||
const kickedPlayer = getUserById(slot.playerId);
|
||||
// Remove the kicked player's referance to the slot they were in
|
||||
kickedPlayer.matchSlotId = -1;
|
||||
// Lock the slot the kicked player was in
|
||||
slot.playerId = -1;
|
||||
slot.status = 2;
|
||||
// Remove the kicked player from the match's stream
|
||||
Streams.removeUserFromStream(MultiplayerMatch.matchStreamName, kickedPlayer.uuid);
|
||||
Streams.removeUserFromStream(MultiplayerMatch.matchChatStreamName, kickedPlayer.uuid);
|
||||
// Remove the kicked player's referance this this match
|
||||
kickedPlayer.currentMatch = null;
|
||||
|
||||
// Inform the kicked user's client that they were kicked
|
||||
osuPacketWriter.MatchUpdate(MultiplayerMatch.createOsuMatchJSON());
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "You were eliminated from the match!",
|
||||
target: global.botUser.username,
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
|
||||
kickedPlayer.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: `${kickedPlayer.username} was eliminated from the match!`,
|
||||
target: "#multiplayer",
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
|
||||
Streams.sendToStream(MultiplayerMatch.matchChatStreamName, osuPacketWriter.toBuffer, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getRemainingPlayerCount(playerScores = [{playerId:0,slotId:0,score:0,isCurrentlyFailed:false}], MultiplayerMatch) {
|
||||
let numberOfPlayersRemaining = 0;
|
||||
for (let playerScore of playerScores) {
|
||||
const slot = MultiplayerMatch.slots[playerScore.slotId];
|
||||
|
||||
if (slot.playerId !== -1 && slot.status !== 2) {
|
||||
numberOfPlayersRemaining++;
|
||||
}
|
||||
}
|
||||
|
||||
return numberOfPlayersRemaining;
|
||||
}
|
||||
|
||||
module.exports = class {
|
||||
constructor(MultiplayerMatchClass = new MultiplayerMatch) {
|
||||
this.name = "osu! Battle Royale";
|
||||
this.MultiplayerMatch = MultiplayerMatchClass;
|
||||
}
|
||||
|
||||
onMatchFinished(playerScores = [{playerId:0,slotId:0,score:0,isCurrentlyFailed:false}]) {
|
||||
let lowestScore = 8589934588;
|
||||
// Find the lowest score
|
||||
for (let i = 0; i < playerScores.length; i++) {
|
||||
const playerScore = playerScores[i];
|
||||
if (playerScore.score < lowestScore) lowestScore = playerScore.score;
|
||||
}
|
||||
|
||||
// Check if everyone has the same score, we don't need to kick anyone if they do.
|
||||
if (sameScoreCheck(playerScores)) return;
|
||||
|
||||
// Kick everyone with the lowest score
|
||||
kickLowScorers(playerScores, this.MultiplayerMatch);
|
||||
|
||||
// Get number of players remaining
|
||||
let numberOfPlayersRemaining = getRemainingPlayerCount(playerScores, this.MultiplayerMatch);
|
||||
|
||||
let playerClassContainer = null;
|
||||
let remainingWriterContainer = null;
|
||||
let i = 0;
|
||||
|
||||
if (numberOfPlayersRemaining == 1) {
|
||||
for (let i1 = 0; i1 < playerScores.length; i++) {
|
||||
const slot = this.MultiplayerMatch.slots[playerScores[i].slotId];
|
||||
if (slot.playerId !== -1 && slot.status !== 2) {
|
||||
playerClassContainer = getUserById(slot.playerId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (numberOfPlayersRemaining) {
|
||||
case 0:
|
||||
remainingWriterContainer = new osu.Bancho.Writer;
|
||||
remainingWriterContainer.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "Everyone was eliminated from the match! Nobody wins.",
|
||||
target: global.botUser.username,
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
for (i = 0; i < playerScores.length; i++) {
|
||||
playerClassContainer = getUserById(playerScores[i].playerId);
|
||||
playerClassContainer.addActionToQueue(remainingWriterContainer.toBuffer);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
remainingWriterContainer = new osu.Bancho.Writer;
|
||||
remainingWriterContainer.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "You are the last one remaining, you win!",
|
||||
target: global.botUser.username,
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
playerClassContainer.addActionToQueue(remainingWriterContainer.toBuffer);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update match for players in the match
|
||||
this.MultiplayerMatch.sendMatchUpdate();
|
||||
// Update the match listing for users in the multiplayer lobby
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
}
|
||||
}
|
|
@ -1,227 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
UserPresenceBundle = require("./Packets/UserPresenceBundle.js"),
|
||||
UserPresence = require("./Packets/UserPresence.js"),
|
||||
StatusUpdate = require("./Packets/StatusUpdate.js"),
|
||||
MultiplayerMatch = require("./MultiplayerMatch.js"),
|
||||
Streams = require("./Streams.js"),
|
||||
User = require("./User.js");
|
||||
|
||||
module.exports = class {
|
||||
constructor() {
|
||||
this.matches = [];
|
||||
}
|
||||
|
||||
userEnterLobby(currentUser) {
|
||||
// If the user is currently already in a match force them to leave
|
||||
if (currentUser.currentMatch != null)
|
||||
currentUser.currentMatch.leaveMatch(currentUser);
|
||||
|
||||
// Add user to the stream for the lobby
|
||||
Streams.addUserToStream("multiplayer_lobby", currentUser.uuid);
|
||||
|
||||
// Send user ids of all online users to all users in the lobby
|
||||
Streams.sendToStream("multiplayer_lobby", UserPresenceBundle(currentUser, false), null);
|
||||
|
||||
// Loop through all matches
|
||||
for (let i = 0; i < this.matches.length; i++) {
|
||||
// Loop through all the users in this match
|
||||
for (let i1 = 0; i1 < this.matches[i].slots.length; i1++) {
|
||||
const slot = this.matches[i].slots[i1];
|
||||
// Make sure there is a player / the slot is not locked
|
||||
if (slot.playerId == -1 || slot.status == 2) continue;
|
||||
|
||||
// Send information for this user to all users in the lobby
|
||||
Streams.sendToStream("multiplayer_lobby", UserPresence(currentUser, slot.playerId, false), null);
|
||||
Streams.sendToStream("multiplayer_lobby", StatusUpdate(currentUser, slot.playerId, false), null);
|
||||
}
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// List the match on the client
|
||||
osuPacketWriter.MatchNew(this.matches[i].createOsuMatchJSON());
|
||||
|
||||
currentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Add the user to the #lobby channel
|
||||
if (!Streams.isUserInStream("#lobby", currentUser.uuid)) {
|
||||
Streams.addUserToStream("#lobby", currentUser.uuid);
|
||||
osuPacketWriter.ChannelJoinSuccess("#lobby");
|
||||
}
|
||||
|
||||
currentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
|
||||
userLeaveLobby(currentUser) {
|
||||
// Remove user from the stream for the multiplayer lobby if they are a part of it
|
||||
if (Streams.isUserInStream("multiplayer_lobby", currentUser.uuid))
|
||||
Streams.removeUserFromStream("multiplayer_lobby", currentUser.uuid);
|
||||
}
|
||||
|
||||
updateMatchListing() {
|
||||
// Send user ids of all online users to all users in the lobby
|
||||
Streams.sendToStream("multiplayer_lobby", UserPresenceBundle(null, false), null);
|
||||
|
||||
// List through all matches
|
||||
for (let i = 0; i < this.matches.length; i++) {
|
||||
// List through all users in the match
|
||||
for (let i1 = 0; i1 < this.matches[i].slots.length; i1++) {
|
||||
const slot = this.matches[i].slots[i1];
|
||||
// Make sure the slot has a user in it / isn't locked
|
||||
if (slot.playerId == -1 || slot.status == 2) continue;
|
||||
|
||||
// Send information for this user to all users in the lobby
|
||||
Streams.sendToStream("multiplayer_lobby", UserPresence(null, slot.playerId, false), null);
|
||||
Streams.sendToStream("multiplayer_lobby", StatusUpdate(null, slot.playerId, false), null);
|
||||
}
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// List the match on the client
|
||||
osuPacketWriter.MatchNew(this.matches[i].createOsuMatchJSON());
|
||||
|
||||
// Send this data back to every user in the lobby
|
||||
Streams.sendToStream("multiplayer_lobby", osuPacketWriter.toBuffer, null);
|
||||
}
|
||||
}
|
||||
|
||||
async createMultiplayerMatch(MatchHost, MatchData) {
|
||||
let matchClass = null;
|
||||
this.matches.push(matchClass = await MultiplayerMatch.createMatch(MatchHost, MatchData));
|
||||
|
||||
// Join the user to the newly created match
|
||||
this.joinMultiplayerMatch(MatchHost, {
|
||||
matchId: matchClass.matchId,
|
||||
gamePassword: matchClass.gamePassword
|
||||
});
|
||||
}
|
||||
|
||||
joinMultiplayerMatch(JoiningUser, JoinInfo) {
|
||||
try {
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
const osuPacketWriter1 = new osu.Bancho.Writer;
|
||||
|
||||
let matchIndex = 0;
|
||||
for (let i = 0; i < this.matches.length; i++) {
|
||||
if (this.matches[i].matchId == JoinInfo.matchId) {
|
||||
matchIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const streamName = this.matches[matchIndex].matchStreamName;
|
||||
const chatStreamName = this.matches[matchIndex].matchChatStreamName;
|
||||
const match = this.matches[matchIndex];
|
||||
|
||||
let full = true;
|
||||
// Loop through all slots to find an empty one
|
||||
for (let i = 0; i < match.slots.length; i++) {
|
||||
const slot = match.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 = JoiningUser.id;
|
||||
JoiningUser.matchSlotId = i;
|
||||
slot.status = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
const matchJSON = match.createOsuMatchJSON();
|
||||
osuPacketWriter1.MatchUpdate(matchJSON);
|
||||
osuPacketWriter.MatchJoinSuccess(matchJSON);
|
||||
|
||||
if (full) {
|
||||
throw "MatchFullException";
|
||||
}
|
||||
|
||||
// Set the user's current match to this match
|
||||
JoiningUser.currentMatch = match;
|
||||
JoiningUser.inMatch = true;
|
||||
|
||||
// Add user to the stream for the match
|
||||
Streams.addUserToStream(streamName, JoiningUser.uuid);
|
||||
Streams.addUserToStream(chatStreamName, JoiningUser.uuid);
|
||||
|
||||
// Inform all users in the match that a new user has joined
|
||||
Streams.sendToStream(streamName, osuPacketWriter1.toBuffer, null);
|
||||
|
||||
osuPacketWriter.ChannelJoinSuccess("#multiplayer");
|
||||
|
||||
// Inform joining client they they have joined the match
|
||||
JoiningUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
// Update the match listing for all users in the lobby since
|
||||
// A user has joined a match
|
||||
this.updateMatchListing();
|
||||
} catch (e) {
|
||||
// Inform the client that there was an issue joining the match
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.MatchJoinFail();
|
||||
|
||||
JoiningUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
this.updateMatchListing();
|
||||
}
|
||||
}
|
||||
|
||||
async leaveMultiplayerMatch(MatchUser = new User) {
|
||||
// Make sure the user is in a match
|
||||
if (MatchUser.currentMatch == null) return;
|
||||
|
||||
const mpMatch = MatchUser.currentMatch;
|
||||
|
||||
mpMatch.leaveMatch(MatchUser);
|
||||
|
||||
let empty = true;
|
||||
// Check if the match is empty
|
||||
for (let i = 0; i < mpMatch.slots.length; i++) {
|
||||
const slot = mpMatch.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 < this.matches.length; i++) {
|
||||
// If the match matches the match the user has left
|
||||
if (this.matches[i].matchStreamName == MatchUser.currentMatch.matchStreamName) {
|
||||
matchIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we got a match index
|
||||
if (matchIndex == null) return;
|
||||
|
||||
// Remove this match from the list of active matches
|
||||
this.matches.splice(matchIndex, 1);
|
||||
|
||||
// Close the db match
|
||||
global.DatabaseHelper.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE id = ?", [mpMatch.matchId]);
|
||||
}
|
||||
|
||||
MatchUser.currentMatch = null;
|
||||
MatchUser.matchSlotId = -1;
|
||||
|
||||
MatchUser.inMatch = false;
|
||||
|
||||
// Update the match listing to reflect this change (either removal or user leaving)
|
||||
this.updateMatchListing();
|
||||
}
|
||||
|
||||
getMatch(MatchID) {
|
||||
for (let match in this.matches) {
|
||||
if (match.matchId == MatchID) return match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,564 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
getUserById = require("./util/getUserById.js"),
|
||||
StatusUpdate = require("./Packets/StatusUpdate.js"),
|
||||
Streams = require("./Streams.js"),
|
||||
User = require("./User.js");
|
||||
|
||||
// TODO: Cache the player's slot position in their user class for a small optimisation
|
||||
|
||||
class MultiplayerMatch {
|
||||
constructor(MatchData = {matchId: -1,inProgress: false,matchType: 0,activeMods: 0,gameName: "",gamePassword: '',beatmapName: '',beatmapId: 0,beatmapChecksum: '',slots: [],host: 0,playMode: 0,matchScoringType: 0,matchTeamType: 0,specialModes: 0,seed: 0}) {
|
||||
this.matchId = MatchData.matchId;
|
||||
|
||||
this.roundId = 0;
|
||||
|
||||
this.inProgress = MatchData.inProgress;
|
||||
this.matchStartCountdownActive = false;
|
||||
|
||||
this.matchType = MatchData.matchType;
|
||||
|
||||
this.activeMods = MatchData.activeMods;
|
||||
|
||||
this.gameName = MatchData.gameName;
|
||||
if (MatchData.gamePassword == '') MatchData.gamePassword == null;
|
||||
this.gamePassword = MatchData.gamePassword;
|
||||
|
||||
this.beatmapName = MatchData.beatmapName;
|
||||
this.beatmapId = MatchData.beatmapId;
|
||||
this.beatmapChecksum = MatchData.beatmapChecksum;
|
||||
|
||||
this.slots = MatchData.slots;
|
||||
for (let i = 0; i < this.slots.length; i++) {
|
||||
this.slots[i].mods = 0;
|
||||
}
|
||||
|
||||
this.host = MatchData.host;
|
||||
|
||||
this.playMode = MatchData.playMode;
|
||||
|
||||
this.matchScoringType = MatchData.matchScoringType;
|
||||
this.matchTeamType = MatchData.matchTeamType;
|
||||
this.specialModes = MatchData.specialModes;
|
||||
|
||||
this.seed = MatchData.seed;
|
||||
|
||||
this.matchStreamName = `mp_${this.matchId}`;
|
||||
this.matchChatStreamName = `mp_chat_${this.matchId}`;
|
||||
|
||||
this.matchLoadSlots = null;
|
||||
this.matchSkippedSlots = null;
|
||||
|
||||
this.playerScores = null;
|
||||
|
||||
this.multiplayerExtras = null;
|
||||
|
||||
this.isTourneyMatch = false;
|
||||
this.tourneyClientUsers = [];
|
||||
}
|
||||
|
||||
static createMatch(MatchHost = new User, MatchData = {matchId: -1,inProgress: false,matchType: 0,activeMods: 0,gameName: "",gamePassword: '',beatmapName: '',beatmapId: 0,beatmapChecksum: '',slots: [],host: 0,playMode: 0,matchScoringType: 0,matchTeamType: 0,specialModes: 0,seed: 0}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
MatchData.matchId = (await global.DatabaseHelper.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 MultiplayerMatch(MatchData);
|
||||
|
||||
console.log(matchInstance.matchId);
|
||||
|
||||
// Update the status of the current user
|
||||
StatusUpdate(MatchHost, MatchHost.id);
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.MatchNew(matchInstance.createOsuMatchJSON());
|
||||
|
||||
MatchHost.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
Streams.addStream(matchInstance.matchStreamName, true, matchInstance.matchId);
|
||||
Streams.addStream(matchInstance.matchChatStreamName, true, matchInstance.matchId);
|
||||
|
||||
// Update the match listing for users in the multiplayer lobby
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
|
||||
resolve(matchInstance);
|
||||
});
|
||||
}
|
||||
|
||||
getSlotIdByPlayerId(playerId = 0) {
|
||||
const player = getUserById(playerId);
|
||||
|
||||
if (player != null) return player.matchSlotId;
|
||||
else return null;
|
||||
}
|
||||
|
||||
createOsuMatchJSON() {
|
||||
return {
|
||||
matchId: this.matchId,
|
||||
inProgress: this.inProgress,
|
||||
matchType: this.matchType,
|
||||
activeMods: this.activeMods,
|
||||
gameName: this.gameName,
|
||||
gamePassword: this.gamePassword,
|
||||
beatmapName: this.beatmapName,
|
||||
beatmapId: this.beatmapId,
|
||||
beatmapChecksum: this.beatmapChecksum,
|
||||
slots: this.slots,
|
||||
host: this.host,
|
||||
playMode: this.playMode,
|
||||
matchScoringType: this.matchScoringType,
|
||||
matchTeamType: this.matchTeamType,
|
||||
specialModes: this.specialModes,
|
||||
seed: this.seed
|
||||
};
|
||||
}
|
||||
|
||||
leaveMatch(MatchUser = new User) {
|
||||
// Make sure this leave call is valid
|
||||
if (!MatchUser.inMatch) return;
|
||||
|
||||
// Get the user's slot
|
||||
const slot = this.slots[MatchUser.matchSlotId];
|
||||
|
||||
// Set the slot's status to avaliable
|
||||
slot.playerId = -1;
|
||||
slot.status = 1;
|
||||
|
||||
// Remove the leaving user from the match's stream
|
||||
Streams.removeUserFromStream(this.matchStreamName, MatchUser.uuid);
|
||||
Streams.removeUserFromStream(this.matchChatStreamName, MatchUser.uuid);
|
||||
|
||||
// Send this after removing the user from match streams to avoid a leave notification for self
|
||||
this.sendMatchUpdate();
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Remove user from the multiplayer channel for the match
|
||||
osuPacketWriter.ChannelRevoked("#multiplayer");
|
||||
|
||||
MatchUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
|
||||
async updateMatch(MatchUser = new User, MatchData) {
|
||||
// Update match with new data
|
||||
this.inProgress = MatchData.inProgress;
|
||||
|
||||
this.matchType = MatchData.matchType;
|
||||
|
||||
this.activeMods = MatchData.activeMods;
|
||||
|
||||
const gameNameChanged = this.gameName !== MatchData.gameName;
|
||||
this.gameName = MatchData.gameName;
|
||||
|
||||
if (MatchData.gamePassword == '') MatchData.gamePassword == null;
|
||||
this.gamePassword = MatchData.gamePassword;
|
||||
|
||||
this.beatmapName = MatchData.beatmapName;
|
||||
this.beatmapId = MatchData.beatmapId;
|
||||
this.beatmapChecksum = MatchData.beatmapChecksum;
|
||||
|
||||
this.host = MatchData.host;
|
||||
|
||||
this.playMode = MatchData.playMode;
|
||||
|
||||
this.matchScoringType = MatchData.matchScoringType;
|
||||
this.matchTeamType = MatchData.matchTeamType;
|
||||
this.specialModes = MatchData.specialModes;
|
||||
|
||||
const gameSeedChanged = this.seed !== MatchData.seed;
|
||||
this.seed = MatchData.seed;
|
||||
|
||||
if (gameNameChanged || gameSeedChanged) {
|
||||
const queryData = [];
|
||||
if (gameNameChanged) {
|
||||
queryData.push(MatchData.gameName);
|
||||
}
|
||||
if (gameSeedChanged) {
|
||||
queryData.push(MatchData.seed);
|
||||
}
|
||||
queryData.push(this.matchId);
|
||||
|
||||
await global.DatabaseHelper.query(`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, queryData);
|
||||
}
|
||||
|
||||
this.sendMatchUpdate();
|
||||
|
||||
// Update the match listing in the lobby to reflect these changes
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
}
|
||||
|
||||
sendMatchUpdate() {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.MatchUpdate(this.createOsuMatchJSON());
|
||||
|
||||
// Update all users in the match with new match information
|
||||
if (Streams.exists(this.matchStreamName))
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
}
|
||||
|
||||
moveToSlot(MatchUser = new User, SlotToMoveTo) {
|
||||
const oldSlot = this.slots[MatchUser.matchSlotId];
|
||||
|
||||
// Set the new slot's data to the user's old slot data
|
||||
this.slots[SlotToMoveTo].playerId = MatchUser.id;
|
||||
MatchUser.matchSlotId = SlotToMoveTo;
|
||||
this.slots[SlotToMoveTo].status = 4;
|
||||
|
||||
// Set the old slot's data to open
|
||||
oldSlot.playerId = -1;
|
||||
oldSlot.status = 1;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
|
||||
// Update the match listing in the lobby to reflect this change
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
}
|
||||
|
||||
changeTeam(MatchUser = new User) {
|
||||
const slot = this.slots[MatchUser.matchSlotId];
|
||||
slot.team = slot.team == 0 ? 1 : 0;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
setStateReady(MatchUser = new User) {
|
||||
if (!MatchUser.inMatch) return;
|
||||
|
||||
// Set the user's ready state to ready
|
||||
this.slots[MatchUser.matchSlotId].status = 8;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
setStateNotReady(MatchUser = new User) {
|
||||
if (!MatchUser.inMatch) return;
|
||||
|
||||
// Set the user's ready state to not ready
|
||||
this.slots[MatchUser.matchSlotId].status = 4;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
lockMatchSlot(MatchUser = new User, MatchUserToKick) {
|
||||
// Make sure the user attempting to kick / lock is the host of the match
|
||||
if (this.host != MatchUser.id) return;
|
||||
|
||||
// Make sure the user that is attempting to be kicked is not the host
|
||||
if (this.slots[MatchUserToKick].playerId === this.host) return;
|
||||
|
||||
// Get the data of the slot at the index sent by the client
|
||||
const slot = this.slots[MatchUserToKick];
|
||||
|
||||
let isSlotEmpty = true;
|
||||
|
||||
// If the slot is empty lock/unlock instead of kicking
|
||||
if (slot.playerId === -1)
|
||||
slot.status = slot.status === 1 ? 2 : 1;
|
||||
|
||||
// The slot isn't empty, kick the player
|
||||
else {
|
||||
const kickedPlayer = getUserById(slot.playerId);
|
||||
kickedPlayer.matchSlotId = -1;
|
||||
slot.playerId = -1;
|
||||
slot.status = 1;
|
||||
isSlotEmpty = false;
|
||||
}
|
||||
|
||||
this.sendMatchUpdate();
|
||||
|
||||
// Update the match listing in the lobby listing to reflect this change
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
|
||||
if (!isSlotEmpty) {
|
||||
let cachedPlayerToken = getUserById(slot.playerId).uuid;
|
||||
|
||||
if (cachedPlayerToken !== null && cachedPlayerToken !== "") {
|
||||
// Remove the kicked user from the match stream
|
||||
Streams.removeUserFromStream(this.matchStreamName, cachedPlayerToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
missingBeatmap(MatchUser = new User) {
|
||||
// User is missing the beatmap set the status to reflect it
|
||||
this.slots[MatchUser.matchSlotId].status = 16;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
notMissingBeatmap(MatchUser = new User) {
|
||||
// The user is not missing the beatmap, set the status to normal
|
||||
this.slots[MatchUser.matchSlotId].status = 4;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
matchSkip(MatchUser = new User) {
|
||||
if (this.matchSkippedSlots == null) {
|
||||
this.matchSkippedSlots = [];
|
||||
|
||||
const skippedSlots = this.matchSkippedSlots;
|
||||
|
||||
for (let slot of this.slots) {
|
||||
// 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});
|
||||
}
|
||||
}
|
||||
|
||||
let allSkipped = true;
|
||||
for (let skippedSlot of this.matchSkippedSlots) {
|
||||
// If loadslot belongs to this user then set loaded to true
|
||||
if (skippedSlot.playerId == MatchUser.id) {
|
||||
skippedSlot.skipped = true;
|
||||
}
|
||||
|
||||
if (skippedSlot.skipped) continue;
|
||||
|
||||
// A user hasn't skipped
|
||||
allSkipped = false;
|
||||
}
|
||||
|
||||
// All players have finished playing, finish the match
|
||||
if (allSkipped) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.MatchPlayerSkipped(MatchUser.id);
|
||||
osuPacketWriter.MatchSkip();
|
||||
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
|
||||
this.matchSkippedSlots = null;
|
||||
} else {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.MatchPlayerSkipped(MatchUser.id);
|
||||
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
}
|
||||
}
|
||||
|
||||
transferHost(MatchUser = new User, SlotIDToTransferTo) {
|
||||
// Set the lobby's host to the new user
|
||||
this.host = this.slots[SlotIDToTransferTo].playerId;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
// TODO: Fix not being able to add DT when freemod is active
|
||||
updateMods(MatchUser = new User, MatchMods) {
|
||||
// Check if freemod is enabled
|
||||
if (this.specialModes === 1) {
|
||||
this.slots[MatchUser.matchSlotId].mods = MatchMods;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
} else {
|
||||
// Make sure the person updating mods is the host of the match
|
||||
if (this.host !== MatchUser.id) return;
|
||||
|
||||
// Change the matches mods to these new mods
|
||||
// TODO: Do this per user if freemod is enabled
|
||||
this.activeMods = MatchMods;
|
||||
|
||||
this.sendMatchUpdate();
|
||||
}
|
||||
|
||||
// Update match listing in the lobby to reflect this change
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
}
|
||||
|
||||
startMatch() {
|
||||
// Make sure the match is not already in progress
|
||||
// The client sometimes double fires the start packet
|
||||
if (this.inProgress) return;
|
||||
this.inProgress = true;
|
||||
// Create array for monitoring users until they are ready to play
|
||||
this.matchLoadSlots = [];
|
||||
// Loop through all slots in the match
|
||||
for (let slot of this.slots) {
|
||||
// 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
|
||||
this.matchLoadSlots.push({
|
||||
playerId: slot.playerId,
|
||||
loaded: false
|
||||
});
|
||||
|
||||
// Set the user's status to playing
|
||||
slot.status = 32;
|
||||
}
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.MatchStart(this.createOsuMatchJSON());
|
||||
|
||||
// Inform all users in the match that it has started
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
|
||||
// Update all users in the match with new info
|
||||
this.sendMatchUpdate();
|
||||
|
||||
// Update match listing in lobby to show the game is in progress
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
}
|
||||
|
||||
matchPlayerLoaded(MatchUser = new User) {
|
||||
// Loop through all user load check items and check if all users are loaded
|
||||
let allLoaded = true;
|
||||
for (let loadedSlot of this.matchLoadSlots) {
|
||||
// If loadslot belongs to this user then set loaded to true
|
||||
if (loadedSlot.playerId == MatchUser.id) {
|
||||
loadedSlot.loaded = true;
|
||||
}
|
||||
|
||||
if (loadedSlot.loaded) continue;
|
||||
|
||||
allLoaded = false;
|
||||
}
|
||||
|
||||
// All players have loaded the beatmap, start playing.
|
||||
if (allLoaded) {
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.MatchAllPlayersLoaded();
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
|
||||
// Blank out user loading array
|
||||
this.matchLoadSlots = null;
|
||||
|
||||
this.playerScores = [];
|
||||
for (let i = 0; i < this.slots.length; i++) {
|
||||
const slot = this.slots[i];
|
||||
if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) continue;
|
||||
|
||||
this.playerScores.push({playerId: slot.playerId, slotId: i, score: 0, isCurrentlyFailed: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onPlayerFinishMatch(MatchUser = new User) {
|
||||
if (this.matchLoadSlots == null) {
|
||||
// Repopulate user loading slots again
|
||||
this.matchLoadSlots = [];
|
||||
for (let slot of this.slots) {
|
||||
// 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
|
||||
this.matchLoadSlots.push({
|
||||
playerId: slot.playerId,
|
||||
loaded: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let allLoaded = true;
|
||||
|
||||
// Loop through all loaded slots to make sure all users have finished playing
|
||||
for (let loadedSlot of this.matchLoadSlots) {
|
||||
if (loadedSlot.playerId == MatchUser.id) {
|
||||
loadedSlot.loaded = true;
|
||||
}
|
||||
|
||||
if (loadedSlot.loaded) continue;
|
||||
|
||||
// A user hasn't finished playing
|
||||
allLoaded = false;
|
||||
}
|
||||
|
||||
// All players have finished playing, finish the match
|
||||
if (allLoaded) await this.finishMatch();
|
||||
}
|
||||
|
||||
async finishMatch() {
|
||||
if (!this.inProgress) return;
|
||||
this.matchLoadSlots = null;
|
||||
this.inProgress = false;
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
let queryData = [this.matchId, this.roundId++, this.playMode, this.matchType, this.matchScoringType, this.matchTeamType, this.activeMods, this.beatmapChecksum, (this.specialModes === 1) ? 1 : 0];
|
||||
|
||||
// Loop through all slots in the match
|
||||
for (let slot of this.slots) {
|
||||
// Make sure the slot has a user
|
||||
if (slot.playerId === -1 || slot.status === 1 || slot.status === 2) {
|
||||
queryData.push(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
let score = null;
|
||||
for (let _playerScore of this.playerScores) {
|
||||
if (_playerScore.playerId === slot.playerId) {
|
||||
score = _playerScore._raw;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
queryData.push(`${slot.playerId}|${score.totalScore}|${score.maxCombo}|${score.count300}|${score.count100}|${score.count50}|${score.countGeki}|${score.countKatu}|${score.countMiss}|${(score.currentHp == 254) ? 1 : 0}${(this.specialModes === 1) ? `|${slot.mods}` : ""}|${score.usingScoreV2 ? 1 : 0}${score.usingScoreV2 ? `|${score.comboPortion}|${score.bonusPortion}` : ""}`);
|
||||
|
||||
// Set the user's status back to normal from playing
|
||||
slot.status = 4;
|
||||
}
|
||||
|
||||
console.log(queryData);
|
||||
|
||||
osuPacketWriter.MatchComplete();
|
||||
|
||||
// Inform all users in the match that it is complete
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
|
||||
// Update all users in the match with new info
|
||||
this.sendMatchUpdate();
|
||||
|
||||
// Update match info in the lobby to reflect that the match has finished
|
||||
global.MultiplayerManager.updateMatchListing();
|
||||
|
||||
if (this.multiplayerExtras != null) this.multiplayerExtras.onMatchFinished(JSON.parse(JSON.stringify(this.playerScores)));
|
||||
|
||||
await global.DatabaseHelper.query("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData);
|
||||
|
||||
this.playerScores = null;
|
||||
}
|
||||
|
||||
updatePlayerScore(MatchPlayer = new User, MatchScoreData) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Make sure the user's slot ID is not invalid
|
||||
if (this.matchSlotId == -1) return;
|
||||
|
||||
// Get the user's current slotID and append it to the givien data, just incase.
|
||||
MatchScoreData.id = MatchPlayer.matchSlotId;
|
||||
|
||||
// Update the playerScores array accordingly
|
||||
for (let playerScore of this.playerScores) {
|
||||
if (playerScore.playerId == MatchPlayer.id) {
|
||||
playerScore.score = MatchScoreData.totalScore;
|
||||
playerScore.isCurrentlyFailed = MatchScoreData.currentHp == 254;
|
||||
playerScore._raw = MatchScoreData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
osuPacketWriter.MatchScoreUpdate(MatchScoreData);
|
||||
|
||||
// Send the newly updated score to all users in the match
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
}
|
||||
|
||||
matchFailed(MatchUser = new User) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Make sure the user's slot ID is not invalid
|
||||
if (MatchUser.matchSlotId == -1) return;
|
||||
|
||||
osuPacketWriter.MatchPlayerFailed(MatchUser.id);
|
||||
|
||||
Streams.sendToStream(this.matchStreamName, osuPacketWriter.toBuffer, null);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MultiplayerMatch;
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = function(CurrentUser, FriendToAdd) {
|
||||
global.DatabaseHelper.query("INSERT INTO friends (user, friendsWith) VALUES (?, ?);", [CurrentUser.id, FriendToAdd]);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
const StatusUpdate = require("./StatusUpdate.js"),
|
||||
Streams = require("../Streams.js");
|
||||
|
||||
module.exports = function(currentUser, data) {
|
||||
currentUser.updatePresence(data);
|
||||
|
||||
if (Streams.exists(`sp_${currentUser.username}`)) {
|
||||
const statusUpdate = StatusUpdate(currentUser, currentUser.id, false);
|
||||
Streams.sendToStream(`sp_${currentUser.username}`, statusUpdate, null);
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
consoleHelper = require("../../consoleHelper.js"),
|
||||
Streams = require("../Streams.js");
|
||||
|
||||
module.exports = function(CurrentUser, channelName = "") {
|
||||
// Make sure the user is not already in the channel
|
||||
if (Streams.isUserInStream(channelName, CurrentUser.uuid))
|
||||
return consoleHelper.printBancho(`Did not add user to channel ${channelName} because they are already in it`);
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.ChannelJoinSuccess(channelName);
|
||||
if (!Streams.isUserInStream(channelName, CurrentUser.uuid))
|
||||
Streams.addUserToStream(channelName, CurrentUser.uuid);
|
||||
|
||||
CurrentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
const Streams = require("../Streams.js");
|
||||
|
||||
module.exports = function(CurrentUser, data) {
|
||||
if (data == "#multiplayer") return; // Ignore requests for multiplayer
|
||||
|
||||
Streams.removeUserFromStream(data, CurrentUser.uuid);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
const consoleHelper = require("../../consoleHelper.js"),
|
||||
Streams = require("../Streams.js");
|
||||
|
||||
module.exports = async function(CurrentUser) {
|
||||
if (CurrentUser.uuid === "bot") throw "Tried to log bot out, WTF???";
|
||||
|
||||
const logoutStartTime = Date.now();
|
||||
|
||||
const streamList = Streams.getStreams();
|
||||
|
||||
for (let i = 0; i < streamList.length; i++) {
|
||||
if (Streams.isUserInStream(streamList[i], CurrentUser.uuid)) {
|
||||
Streams.removeUserFromStream(streamList[i], CurrentUser.uuid);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove user from user list
|
||||
global.users.remove(CurrentUser.uuid);
|
||||
|
||||
await global.DatabaseHelper.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [global.users.getLength() - 1]);
|
||||
|
||||
consoleHelper.printBancho(`User logged out, took ${Date.now() - logoutStartTime}ms. [User: ${CurrentUser.username}]`);
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
getUserById = require("../util/getUserById.js");
|
||||
|
||||
module.exports = function(CurrentUser, InvitedUser) {
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
const InvitedUserClass = getUserById(InvitedUser);
|
||||
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: CurrentUser.username,
|
||||
message: `Come join my multiplayer match: [osump://${CurrentUser.currentMatch.matchId}/ ${CurrentUser.currentMatch.gameName}]`,
|
||||
target: CurrentUser.username,
|
||||
senderId: CurrentUser.id
|
||||
});
|
||||
|
||||
InvitedUserClass.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = function(CurrentUser, FriendToRemove) {
|
||||
global.DatabaseHelper.query("DELETE FROM friends WHERE user = ? AND friendsWith = ? LIMIT 1", [CurrentUser.id, FriendToRemove]);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
getUserByUsername = require("../util/getUserByUsername.js");
|
||||
|
||||
module.exports = function(CurrentUser, CurrentPacket) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
const userSentTo = getUserByUsername(CurrentPacket.target);
|
||||
|
||||
if (userSentTo == null) return;
|
||||
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: CurrentUser.username,
|
||||
message: CurrentPacket.message,
|
||||
target: CurrentUser.username,
|
||||
senderId: CurrentUser.id
|
||||
});
|
||||
|
||||
// Write chat message to stream asociated with chat channel
|
||||
return userSentTo.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
botCommandHandler = require("../BotCommandHandler.js"),
|
||||
consoleHelper = require("../../consoleHelper.js"),
|
||||
Streams = require("../Streams.js");
|
||||
|
||||
module.exports = function(CurrentUser, CurrentPacket) {
|
||||
let isSendingChannelLocked = false;
|
||||
for (let i = 0; i < global.channels.length; i++) {
|
||||
if (!CurrentPacket.target.includes("#")) break;
|
||||
if (global.channels[i].channelName == CurrentPacket.target) {
|
||||
isSendingChannelLocked = global.channels[i].locked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSendingChannelLocked) {
|
||||
if (CurrentPacket.message.includes("!")) {
|
||||
botCommandHandler(CurrentUser, CurrentPacket.message, CurrentPacket.target);
|
||||
} else {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: global.botUser.username,
|
||||
message: "The channel you are currently trying to send to is locked, please check back later!",
|
||||
target: CurrentPacket.target,
|
||||
senderId: global.botUser.id
|
||||
});
|
||||
CurrentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
consoleHelper.printChat(`${CurrentUser.username} in ${CurrentPacket.target} sent: ${CurrentPacket.message}`);
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.SendMessage({
|
||||
sendingClient: CurrentUser.username,
|
||||
message: CurrentPacket.message,
|
||||
target: CurrentPacket.target,
|
||||
senderId: CurrentUser.id
|
||||
});
|
||||
|
||||
if (CurrentPacket.target == "#multiplayer") {
|
||||
Streams.sendToStream(CurrentUser.currentMatch.matchChatStreamName, osuPacketWriter.toBuffer, CurrentUser.uuid);
|
||||
botCommandHandler(CurrentUser, CurrentPacket.message, CurrentUser.currentMatch.matchChatStreamName, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the stream that we're sending to even exists
|
||||
if (!Streams.exists(CurrentPacket.target)) return;
|
||||
|
||||
// Write chat message to stream asociated with chat channel
|
||||
Streams.sendToStream(CurrentPacket.target, osuPacketWriter.toBuffer, CurrentUser.uuid);
|
||||
if (CurrentPacket.target == "#osu")
|
||||
global.addChatMessage(`${CurrentUser.username}: ${CurrentPacket.message.replaceAll("<", "<").replaceAll(">", ">")}`);
|
||||
|
||||
botCommandHandler(CurrentUser, CurrentPacket.message, CurrentPacket.target);
|
||||
return;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = function(CurrentUser, Message) {
|
||||
global.DatabaseHelper.query("UPDATE users_info SET away_message = ? WHERE id = ?", [Message.message, CurrentUser.id]);
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
getUserById = require("../util/getUserById.js");
|
||||
|
||||
module.exports = function(currentUser, id = 0, sendImmidiate = true) {
|
||||
if (id == 3) return; // Ignore Bot
|
||||
|
||||
// Create new osu packet writer
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Get user's class
|
||||
const User = getUserById(id);
|
||||
|
||||
if (User == null) return;
|
||||
|
||||
let UserStatusObject = {
|
||||
userId: User.id,
|
||||
status: User.actionID,
|
||||
statusText: User.actionText,
|
||||
beatmapChecksum: User.beatmapChecksum,
|
||||
currentMods: User.currentMods,
|
||||
playMode: User.playMode,
|
||||
beatmapId: User.beatmapID,
|
||||
rankedScore: User.rankedScore,
|
||||
accuracy: User.accuracy * 0.01, // Scale from 0:100 to 0:1
|
||||
playCount: User.playCount,
|
||||
totalScore: User.totalScore,
|
||||
rank: User.rank,
|
||||
performance: (User.rankingMode == 0 ? User.pp : 0)
|
||||
};
|
||||
|
||||
osuPacketWriter.HandleOsuUpdate(UserStatusObject);
|
||||
|
||||
// Send data to user's queue
|
||||
if (sendImmidiate) currentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
else return osuPacketWriter.toBuffer;
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
consoleHelper = require("./consoleHelper.js"),
|
||||
Streams = require("../Streams.js");
|
||||
|
||||
module.exports = function(CurrentUser, MatchID) {
|
||||
const match = global.MultiplayerManager.getMatch(MatchID);
|
||||
|
||||
if (match != null) {
|
||||
|
||||
match.isTourneyMatch = true;
|
||||
for (let user of global.users.getIterableItems()) {
|
||||
if (user.id == CurrentUser.id) {
|
||||
match.tourneyClientUsers.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
if (Streams.isUserInStream(match.matchChatStreamName, CurrentUser.uuid))
|
||||
return consoleHelper.printBancho(`Did not add user to channel ${match.matchChatStreamName} because they are already in it`);
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.ChannelJoinSuccess("#multiplayer");
|
||||
if (!Streams.isUserInStream(match.matchChatStreamName, CurrentUser.uuid))
|
||||
Streams.addUserToStream(match.matchChatStreamName, CurrentUser.uuid);
|
||||
|
||||
CurrentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
consoleHelper = require("../../consoleHelper.js"),
|
||||
Streams = require("../Streams.js");
|
||||
|
||||
module.exports = function(CurrentUser, MatchID) {
|
||||
const match = global.MultiplayerManager.getMatch(MatchID);
|
||||
|
||||
if (match != null) {
|
||||
|
||||
match.isTourneyMatch = false;
|
||||
match.tourneyClientUsers = [];
|
||||
|
||||
if (Streams.isUserInStream(match.matchChatStreamName, CurrentUser.uuid))
|
||||
return consoleHelper.printBancho(`Did not add user to channel ${match.matchChatStreamName} because they are already in it`);
|
||||
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.ChannelRevoked("#multiplayer");
|
||||
if (!Streams.isUserInStream(match.matchChatStreamName, CurrentUser.uuid))
|
||||
Streams.removeUserFromStream(match.matchChatStreamName, CurrentUser.uuid);
|
||||
|
||||
CurrentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
} else {
|
||||
// Still provide feedback just in case
|
||||
// TODO: Check if this has any effect, if not then remove this.
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
osuPacketWriter.ChannelRevoked("#multiplayer");
|
||||
|
||||
CurrentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
UserPresence = require("./UserPresence.js"),
|
||||
StatusUpdate = require("./StatusUpdate.js");
|
||||
|
||||
module.exports = function(CurrentUser, MatchID) {
|
||||
const matchData = global.MultiplayerManager.getMatch(MatchID);
|
||||
|
||||
if (matchData != null) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer();
|
||||
|
||||
osuPacketWriter.MatchUpdate(matchData.createOsuMatchJSON());
|
||||
|
||||
// Queue info on all the users in the match to the client
|
||||
for (let slot in matchData.slots) {
|
||||
CurrentUser.addActionToQueue(UserPresence(CurrentUser, slot.playerId, false));
|
||||
CurrentUser.addActionToQueue(StatusUpdate(CurrentUser, slot.playerId, false));
|
||||
}
|
||||
|
||||
// Queue data
|
||||
CurrentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
getUserById = require("../util/getUserById.js");
|
||||
|
||||
module.exports = function(currentUser, id = 0, sendImmidiate = true) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
const User = getUserById(id);
|
||||
|
||||
if (User == null) return;
|
||||
|
||||
osuPacketWriter.UserPresence({
|
||||
userId: id,
|
||||
username: User.username,
|
||||
timezone: 0,
|
||||
countryId: User.countryID,
|
||||
permissions: 4,
|
||||
longitude: User.location[1],
|
||||
latitude: User.location[0],
|
||||
rank: User.rank
|
||||
});
|
||||
|
||||
if (sendImmidiate) currentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
else return osuPacketWriter.toBuffer;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
const osu = require("osu-packet");
|
||||
|
||||
module.exports = function(currentUser, sendImmidiate = true) {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
let userIds = [];
|
||||
|
||||
for (let user of global.users.getIterableItems()) {
|
||||
userIds.push(user.id);
|
||||
}
|
||||
|
||||
osuPacketWriter.UserPresenceBundle(userIds);
|
||||
|
||||
if (sendImmidiate) currentUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
else return osuPacketWriter.toBuffer;
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
const UserPresenceBundle = require("./UserPresenceBundle.js"),
|
||||
UserPresence = require("./UserPresence.js"),
|
||||
StatusUpdate = require("./StatusUpdate.js");
|
||||
|
||||
module.exports = function (currentUser, data = [0]) {
|
||||
UserPresenceBundle(currentUser);
|
||||
|
||||
for (let i1 = 0; i1 < data.length; i1++) {
|
||||
const CurrentUserID = data[i1];
|
||||
|
||||
UserPresence(currentUser, CurrentUserID);
|
||||
StatusUpdate(currentUser, CurrentUserID);
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
getUserById = require("./util/getUserById.js"),
|
||||
Streams = require("./Streams.js");
|
||||
|
||||
module.exports = {
|
||||
startSpectatingUser:function(currentUser, spectatedId) {
|
||||
// Get the user this user is trying to spectate
|
||||
const User = getUserById(spectatedId);
|
||||
if (Streams.exists(`sp_${User.id}`)) {
|
||||
// Just add user to stream since it already exists
|
||||
Streams.addUserToStream(`sp_${User.id}`, currentUser.uuid);
|
||||
} else {
|
||||
// Stream doesn't exist, create it and add the spectator
|
||||
Streams.addStream(`sp_${User.id}`, true, spectatedId);
|
||||
Streams.addUserToStream(`sp_${User.id}`, currentUser.uuid);
|
||||
}
|
||||
|
||||
// We want to do this stuff regardless
|
||||
// Create a new osu packet writer
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Set the user requesting to be spectating this user
|
||||
currentUser.spectating = spectatedId;
|
||||
|
||||
// Tell the client of the user being spectated that they are being spectated
|
||||
osuPacketWriter.SpectatorJoined(currentUser.id);
|
||||
|
||||
// Send the packet to the spectated user's queue
|
||||
User.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
// Make a new clear osu packet writer
|
||||
osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Tell everyone spectating this user that another user has started spectating
|
||||
osuPacketWriter.FellowSpectatorJoined(currentUser.id);
|
||||
|
||||
// Send this packet to all the spectators
|
||||
Streams.sendToStream(`sp_${User.id}`, osuPacketWriter.toBuffer);
|
||||
},
|
||||
|
||||
sendSpectatorFrames(currentUser, data) {
|
||||
// Create new osu packet writer
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Data containing the user's actions
|
||||
osuPacketWriter.SpectateFrames(data);
|
||||
|
||||
// Send the frames to all the spectators
|
||||
Streams.sendToStream(`sp_${currentUser.id}`, osuPacketWriter.toBuffer, null);
|
||||
},
|
||||
|
||||
stopSpectatingUser(currentUser) {
|
||||
// Get the user this user is spectating
|
||||
const spectatedUser = getUserById(currentUser.spectating);
|
||||
// Create new osu packet writer
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Inform the client being spectated that this user has stopped spectating
|
||||
osuPacketWriter.SpectatorLeft(currentUser.id);
|
||||
|
||||
// Add this packet to the spectated user's queue
|
||||
spectatedUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||
|
||||
// Remove this user from the spectator stream
|
||||
Streams.removeUserFromStream(`sp_${spectatedUser.id}`, currentUser.uuid);
|
||||
|
||||
// Make a new clear osu packet writer
|
||||
osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// Inform other users spectating that this spectator has left
|
||||
osuPacketWriter.FellowSpectatorLeft(currentUser.id);
|
||||
|
||||
// Send this packet to all spectators
|
||||
Streams.sendToStream(`sp_${spectatedUser.id}`, osuPacketWriter.toBuffer);
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
const getUserByToken = require("./util/getUserByToken.js"),
|
||||
consoleHelper = require("../consoleHelper.js");
|
||||
|
||||
module.exports = class {
|
||||
static init() {
|
||||
global.avaliableStreams = {};
|
||||
global.avaliableStreamKeys = [];
|
||||
}
|
||||
|
||||
static addStream(streamName = "", removeIfEmpty = false, spectatorHostId = null) {
|
||||
// Make sure a stream with the same name doesn't exist already
|
||||
if (global.avaliableStreamKeys.includes(streamName))
|
||||
return consoleHelper.printBancho(`Did not add stream [${streamName}] A stream with the same name already exists`);
|
||||
// Add new stream to the list of streams
|
||||
global.avaliableStreams[streamName] = {
|
||||
streamUsers: [], // An array containing a list of user tokens of the users in a given stream
|
||||
streamSpectatorHost: spectatorHostId, // null unless stream is for spectating
|
||||
removeIfEmpty: removeIfEmpty
|
||||
}
|
||||
global.avaliableStreamKeys = Object.keys(global.avaliableStreams);
|
||||
consoleHelper.printBancho(`Added stream [${streamName}]`);
|
||||
}
|
||||
|
||||
static removeStream(streamName) {
|
||||
try {
|
||||
delete global.avaliableStreams[streamName];
|
||||
global.avaliableStreamKeys = Object.keys(global.avaliableStreams);
|
||||
} catch (e) {
|
||||
consoleHelper.printError(`Was not able to remove stream [${streamName}]`);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
static addUserToStream(streamName, userToken) {
|
||||
// Make sure the stream we are attempting to add this user to even exists
|
||||
if (!this.exists(streamName))
|
||||
return consoleHelper.printBancho(`Did not add user to stream [${streamName}] because it does not exist!`);
|
||||
|
||||
// Make sure the user isn't already in the stream
|
||||
if (global.avaliableStreams[streamName].streamUsers.includes(userToken))
|
||||
return consoleHelper.printBancho(`Did not add user to stream [${streamName}] because they are already in it!`);
|
||||
|
||||
// Make sure this isn't an invalid user (userId can't be lower than 1)
|
||||
if (userToken == "" || userToken == null)
|
||||
return consoleHelper.printBancho(`Did not add user to stream [${streamName}] because their token is invalid!`);
|
||||
|
||||
// Add user's token to the stream's user list
|
||||
global.avaliableStreams[streamName].streamUsers.push(userToken);
|
||||
consoleHelper.printBancho(`Added user [${userToken}] to stream ${streamName}`);
|
||||
}
|
||||
|
||||
static removeUserFromStream(streamName, userToken) {
|
||||
// Make sure the stream we are attempting to add this user to even exists
|
||||
if (!this.exists(streamName))
|
||||
return consoleHelper.printBancho(`Did not remove user from stream [${streamName}] because it does not exist!`);
|
||||
|
||||
const stream = global.avaliableStreams[streamName];
|
||||
|
||||
// Make sure the user isn't already in the stream
|
||||
if (!stream.streamUsers.includes(userToken))
|
||||
return consoleHelper.printBancho(`Did not remove user from stream [${streamName}] because they are not in it!`);
|
||||
|
||||
// Make sure this isn't an invalid user (userId can't be lower than 1)
|
||||
if (userToken == "" || userToken == null)
|
||||
return consoleHelper.printBancho(`Did not remove user from stream [${streamName}] because their userId is invalid!`);
|
||||
try {
|
||||
// Find index of user to remove
|
||||
let userCurrentIndex;
|
||||
for (let i = 0; i < stream.streamUsers.length; i++) {
|
||||
if (userToken == stream.streamUsers[i]) {
|
||||
userCurrentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove user from stream's user list
|
||||
stream.streamUsers.splice(userCurrentIndex, 1);
|
||||
consoleHelper.printBancho(`Removed user [${userToken}] from stream ${streamName}`);
|
||||
} catch (e) {
|
||||
consoleHelper.printBancho(`Can't Remove user [${userToken}] from stream ${streamName}`);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
if (stream.removeIfEmpty && stream.streamUsers.length == 0) {
|
||||
this.removeStream(streamName);
|
||||
consoleHelper.printBancho(`Removed stream [${streamName}] There were no users in stream`);
|
||||
}
|
||||
}
|
||||
|
||||
static sendToStream(streamName, streamData, initUser = null) {
|
||||
// Make sure the stream we are attempting to send to even exists
|
||||
if (!this.exists(streamName))
|
||||
return consoleHelper.printBancho(`Did not send to stream [${streamName}] because it does not exist!`);
|
||||
|
||||
// Get the stream to send the data to
|
||||
const currentStream = global.avaliableStreams[streamName];
|
||||
|
||||
// Loop through the users in this stream
|
||||
for (let i = 0; i < currentStream.streamUsers.length; i++) {
|
||||
// Get the user token of the user in the queue
|
||||
const currentUserToken = currentStream.streamUsers[i];
|
||||
// Make sure we don't send this data back to the user requesting this data to be sent
|
||||
if (initUser != null && currentUserToken == initUser && (streamName[0] == "#" || streamName.includes("mp_"))) continue;
|
||||
if (currentUserToken == 3) continue; // Skip if user is bot
|
||||
|
||||
// Get user object
|
||||
const currentUser = getUserByToken(currentUserToken);
|
||||
// Skip if user is nonexistant
|
||||
if (currentUser == null) continue;
|
||||
|
||||
// Send stream data to user's own queue
|
||||
currentUser.addActionToQueue(streamData);
|
||||
}
|
||||
}
|
||||
|
||||
static exists(streamName) {
|
||||
return global.avaliableStreamKeys.includes(streamName);
|
||||
}
|
||||
|
||||
static getStreams() {
|
||||
// Return the names of all avaliable streams
|
||||
return global.avaliableStreamKeys;
|
||||
}
|
||||
|
||||
static isUserInStream(streamName, userToken) {
|
||||
if (global.avaliableStreams[streamName].streamUsers.includes(userToken)) return true;
|
||||
else return false;
|
||||
}
|
||||
}
|
125
server/User.js
125
server/User.js
|
@ -1,125 +0,0 @@
|
|||
const StatusUpdate = require("./Packets/StatusUpdate.js");
|
||||
|
||||
const rankingModes = [
|
||||
"pp_raw",
|
||||
"ranked_score",
|
||||
"avg_accuracy"
|
||||
];
|
||||
|
||||
module.exports = class {
|
||||
constructor(id, username, uuid) {
|
||||
this.id = id;
|
||||
this.username = username;
|
||||
this.uuid = uuid;
|
||||
this.connectTime = Date.now();
|
||||
this.timeoutTime = Date.now() + 30000;
|
||||
this.queue = Buffer.alloc(0);
|
||||
|
||||
// Binato specific
|
||||
this.rankingMode = 0;
|
||||
|
||||
this.playMode = 0;
|
||||
this.countryID = 0;
|
||||
this.spectators = [];
|
||||
this.spectating = 0;
|
||||
this.location = [0,0];
|
||||
this.joinedChannels = [];
|
||||
|
||||
// Presence data
|
||||
this.actionID = 0;
|
||||
this.actionText = "";
|
||||
this.actionMods = 0;
|
||||
this.beatmapChecksum = "";
|
||||
this.beatmapID = 0;
|
||||
this.currentMods = 0;
|
||||
|
||||
// Cached db data
|
||||
this.rankedScore = 0;
|
||||
this.accuracy = 0;
|
||||
this.playCount = 0;
|
||||
this.totalScore = 0;
|
||||
this.rank = 0;
|
||||
this.pp = 0;
|
||||
|
||||
// Multiplayer data
|
||||
this.currentMatch = null;
|
||||
this.matchSlotId = -1;
|
||||
|
||||
this.inMatch = false;
|
||||
|
||||
this.isTourneyUser = false;
|
||||
}
|
||||
|
||||
// Adds new actions to the user's queue
|
||||
addActionToQueue(newData) {
|
||||
this.queue = Buffer.concat([this.queue, newData], this.queue.length + newData.length);
|
||||
}
|
||||
|
||||
// Updates the user's current action
|
||||
updatePresence(action) {
|
||||
this.actionID = action.status;
|
||||
this.actionText = action.statusText;
|
||||
this.beatmapChecksum = action.beatmapChecksum;
|
||||
this.currentMods = action.currentMods;
|
||||
this.actionMods = action.currentMods;
|
||||
if (action.playMode != this.playMode) {
|
||||
this.updateUserInfo(true);
|
||||
this.playMode = action.playMode;
|
||||
}
|
||||
this.beatmapID = action.beatmapId;
|
||||
}
|
||||
|
||||
// Gets the user's score information from the database and caches it
|
||||
async updateUserInfo(forceUpdate = false) {
|
||||
const userScoreDB = await global.DatabaseHelper.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
|
||||
const mappedRankingMode = rankingModes[this.rankingMode];
|
||||
const userRankDB = await global.DatabaseHelper.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);
|
||||
|
||||
if (userScoreDB == null || userRankDB == null) throw "fuck";
|
||||
|
||||
// Handle "if we should update" checks for each rankingMode
|
||||
let userScoreUpdate = false;
|
||||
switch (this.rankingMode) {
|
||||
case 0:
|
||||
if (this.pp != userScoreDB.pp_raw)
|
||||
userScoreUpdate = true;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (this.rankedScore != userScoreDB.ranked_score)
|
||||
userScoreUpdate = true;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (this.accuracy != userScoreDB.avg_accuracy)
|
||||
userScoreUpdate = true;
|
||||
break;
|
||||
}
|
||||
|
||||
this.rankedScore = userScoreDB.ranked_score;
|
||||
this.totalScore = userScoreDB.total_score;
|
||||
this.accuracy = userScoreDB.avg_accuracy;
|
||||
this.playCount = userScoreDB.playcount;
|
||||
|
||||
// Fetch rank
|
||||
for (let i = 0; i < userRankDB.length; i++) {
|
||||
if (userRankDB[i]["user_id"] == this.id) {
|
||||
this.rank = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set PP to none if ranking mode is not PP
|
||||
if (this.rankingMode == 0) this.pp = userScoreDB.pp_raw;
|
||||
else this.pp = 0;
|
||||
|
||||
if (userScoreUpdate || forceUpdate) {
|
||||
StatusUpdate(this, this.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Clears out the user's queue
|
||||
clearQueue() {
|
||||
this.queue = Buffer.alloc(0);
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
module.exports = function(s) {
|
||||
switch (s) {
|
||||
case "reconnect":
|
||||
return "\u0005\u0000\u0000\u0004\u0000\u0000\u0000<30><30><EFBFBD><EFBFBD>\u0018\u0000\u0000\u0011\u0000\u0000\u0000\u000b\u000fReconnecting...";
|
||||
|
||||
default:
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
const countryCodes = {
|
||||
"LV": 132,
|
||||
"AD": 3,
|
||||
"LT": 130,
|
||||
"KM": 116,
|
||||
"QA": 182,
|
||||
"VA": 0,
|
||||
"PK": 173,
|
||||
"KI": 115,
|
||||
"SS": 0,
|
||||
"KH": 114,
|
||||
"NZ": 166,
|
||||
"TO": 215,
|
||||
"KZ": 122,
|
||||
"GA": 76,
|
||||
"BW": 35,
|
||||
"AX": 247,
|
||||
"GE": 79,
|
||||
"UA": 222,
|
||||
"CR": 50,
|
||||
"AE": 0,
|
||||
"NE": 157,
|
||||
"ZA": 240,
|
||||
"SK": 196,
|
||||
"BV": 34,
|
||||
"SH": 0,
|
||||
"PT": 179,
|
||||
"SC": 189,
|
||||
"CO": 49,
|
||||
"GP": 86,
|
||||
"GY": 93,
|
||||
"CM": 47,
|
||||
"TJ": 211,
|
||||
"AF": 5,
|
||||
"IE": 101,
|
||||
"AL": 8,
|
||||
"BG": 24,
|
||||
"JO": 110,
|
||||
"MU": 149,
|
||||
"PM": 0,
|
||||
"LA": 0,
|
||||
"IO": 104,
|
||||
"KY": 121,
|
||||
"SA": 187,
|
||||
"KN": 0,
|
||||
"OM": 167,
|
||||
"CY": 54,
|
||||
"BQ": 0,
|
||||
"BT": 33,
|
||||
"WS": 236,
|
||||
"ES": 67,
|
||||
"LR": 128,
|
||||
"RW": 186,
|
||||
"AQ": 12,
|
||||
"PW": 180,
|
||||
"JE": 250,
|
||||
"TN": 214,
|
||||
"ZW": 243,
|
||||
"JP": 111,
|
||||
"BB": 20,
|
||||
"VN": 233,
|
||||
"HN": 96,
|
||||
"KP": 0,
|
||||
"WF": 235,
|
||||
"EC": 62,
|
||||
"HU": 99,
|
||||
"GF": 80,
|
||||
"GQ": 87,
|
||||
"TW": 220,
|
||||
"MC": 135,
|
||||
"BE": 22,
|
||||
"PN": 176,
|
||||
"SZ": 205,
|
||||
"CZ": 55,
|
||||
"LY": 0,
|
||||
"IN": 103,
|
||||
"FM": 0,
|
||||
"PY": 181,
|
||||
"PH": 172,
|
||||
"MN": 142,
|
||||
"GG": 248,
|
||||
"CC": 39,
|
||||
"ME": 242,
|
||||
"DO": 60,
|
||||
"KR": 0,
|
||||
"PL": 174,
|
||||
"MT": 148,
|
||||
"MM": 141,
|
||||
"AW": 17,
|
||||
"MV": 150,
|
||||
"BD": 21,
|
||||
"NR": 164,
|
||||
"AT": 15,
|
||||
"GW": 92,
|
||||
"FR": 74,
|
||||
"LI": 126,
|
||||
"CF": 41,
|
||||
"DZ": 61,
|
||||
"MA": 134,
|
||||
"VG": 0,
|
||||
"NC": 156,
|
||||
"IQ": 105,
|
||||
"BN": 0,
|
||||
"BF": 23,
|
||||
"BO": 30,
|
||||
"GB": 77,
|
||||
"CU": 51,
|
||||
"LU": 131,
|
||||
"YT": 238,
|
||||
"NO": 162,
|
||||
"SM": 198,
|
||||
"GL": 83,
|
||||
"IS": 107,
|
||||
"AO": 11,
|
||||
"MH": 138,
|
||||
"SE": 191,
|
||||
"ZM": 241,
|
||||
"FJ": 70,
|
||||
"SL": 197,
|
||||
"CH": 43,
|
||||
"RU": 0,
|
||||
"CW": 0,
|
||||
"CX": 53,
|
||||
"TF": 208,
|
||||
"NL": 161,
|
||||
"AU": 16,
|
||||
"FI": 69,
|
||||
"MS": 147,
|
||||
"GH": 81,
|
||||
"BY": 36,
|
||||
"IL": 102,
|
||||
"VC": 0,
|
||||
"NG": 159,
|
||||
"HT": 98,
|
||||
"LS": 129,
|
||||
"MR": 146,
|
||||
"YE": 237,
|
||||
"MP": 144,
|
||||
"SX": 0,
|
||||
"RE": 183,
|
||||
"RO": 184,
|
||||
"NP": 163,
|
||||
"CG": 0,
|
||||
"FO": 73,
|
||||
"CI": 0,
|
||||
"TH": 210,
|
||||
"HK": 94,
|
||||
"TK": 212,
|
||||
"XK": 0,
|
||||
"DM": 59,
|
||||
"LC": 0,
|
||||
"ID": 100,
|
||||
"MG": 137,
|
||||
"JM": 109,
|
||||
"IT": 108,
|
||||
"CA": 38,
|
||||
"TZ": 221,
|
||||
"GI": 82,
|
||||
"KG": 113,
|
||||
"NU": 165,
|
||||
"TV": 219,
|
||||
"LB": 124,
|
||||
"SY": 0,
|
||||
"PR": 177,
|
||||
"NI": 160,
|
||||
"KE": 112,
|
||||
"MO": 0,
|
||||
"SR": 201,
|
||||
"VI": 0,
|
||||
"SV": 203,
|
||||
"HM": 0,
|
||||
"CD": 0,
|
||||
"BI": 26,
|
||||
"BM": 28,
|
||||
"MW": 151,
|
||||
"TM": 213,
|
||||
"GT": 90,
|
||||
"AG": 0,
|
||||
"UM": 0,
|
||||
"US": 225,
|
||||
"AR": 13,
|
||||
"DJ": 57,
|
||||
"KW": 120,
|
||||
"MY": 153,
|
||||
"FK": 71,
|
||||
"EG": 64,
|
||||
"BA": 0,
|
||||
"CN": 48,
|
||||
"GN": 85,
|
||||
"PS": 178,
|
||||
"SO": 200,
|
||||
"IM": 249,
|
||||
"GS": 0,
|
||||
"BR": 31,
|
||||
"GM": 84,
|
||||
"PF": 170,
|
||||
"PA": 168,
|
||||
"PG": 171,
|
||||
"BH": 25,
|
||||
"TG": 209,
|
||||
"GU": 91,
|
||||
"CK": 45,
|
||||
"MF": 252,
|
||||
"VE": 230,
|
||||
"CL": 46,
|
||||
"TR": 217,
|
||||
"UG": 223,
|
||||
"GD": 78,
|
||||
"TT": 218,
|
||||
"TL": 0,
|
||||
"MD": 0,
|
||||
"MK": 0,
|
||||
"ST": 202,
|
||||
"CV": 52,
|
||||
"MQ": 145,
|
||||
"GR": 88,
|
||||
"HR": 97,
|
||||
"BZ": 37,
|
||||
"UZ": 227,
|
||||
"DK": 58,
|
||||
"SN": 199,
|
||||
"ET": 68,
|
||||
"VU": 234,
|
||||
"ER": 66,
|
||||
"BJ": 27,
|
||||
"LK": 127,
|
||||
"NA": 155,
|
||||
"AS": 14,
|
||||
"SG": 192,
|
||||
"PE": 169,
|
||||
"IR": 0,
|
||||
"MX": 152,
|
||||
"TD": 207,
|
||||
"AZ": 18,
|
||||
"AM": 9,
|
||||
"BL": 0,
|
||||
"SJ": 195,
|
||||
"SB": 188,
|
||||
"NF": 158,
|
||||
"RS": 239,
|
||||
"DE": 56,
|
||||
"EH": 65,
|
||||
"EE": 63,
|
||||
"SD": 190,
|
||||
"ML": 140,
|
||||
"TC": 206,
|
||||
"MZ": 154,
|
||||
"BS": 32,
|
||||
"UY": 226,
|
||||
"SI": 194,
|
||||
"AI": 7
|
||||
}
|
||||
|
||||
const countryCodeKeys = Object.keys(countryCodes);
|
||||
|
||||
module.exports = {
|
||||
getCountryID:function(code = "") {
|
||||
// Get id of a country from a 2 char code
|
||||
code = code.toUpperCase();
|
||||
if (countryCodes[code] != null) return countryCodes[code];
|
||||
else return 0;
|
||||
},
|
||||
|
||||
getCountryLetters:function(code) {
|
||||
// Get country char code from id
|
||||
for (var i = 0; i < countryCodes.length; i++) {
|
||||
const countryId = countryCodes[countryCodeKeys[i]];
|
||||
if (countryId === code) return countryId;
|
||||
}
|
||||
return "XX";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.countryCodes = countryCodes;
|
113
server/enums/Packets.ts
Normal file
113
server/enums/Packets.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
export enum Packets {
|
||||
Client_ChangeAction,
|
||||
Client_SendPublicMessage,
|
||||
Client_Logout,
|
||||
Client_RequestStatusUpdate,
|
||||
Client_Pong,
|
||||
Server_LoginReply,
|
||||
Server_CommandError,
|
||||
Server_SendMessage,
|
||||
Server_Ping,
|
||||
Server_IRCUsernameChange,
|
||||
Server_IRCQuit,
|
||||
Server_UserStats,
|
||||
Server_UserLogout,
|
||||
Server_SpectatorJoined,
|
||||
Server_SpectatorLeft,
|
||||
Server_SpectateFrames,
|
||||
Client_StartSpectating,
|
||||
Client_StopSpectating,
|
||||
Client_SpectateFrames,
|
||||
Server_VersionUpdate,
|
||||
Client_ErrorReport,
|
||||
Client_CantSpectate,
|
||||
Server_SpectatorCantSpectate,
|
||||
Server_GetAttention,
|
||||
Server_Notification,
|
||||
Client_SendPrivateMessage,
|
||||
Server_UpdateMatch,
|
||||
Server_NewMatch,
|
||||
Server_DisposeMatch,
|
||||
Client_PartLobby,
|
||||
Client_JoinLobby,
|
||||
Client_CreateMatch,
|
||||
Client_JoinMatch,
|
||||
Client_PartMatch,
|
||||
Server_LobbyJoin_OLD,
|
||||
Server_LobbyPart_OLD,
|
||||
Server_MatchJoinSuccess,
|
||||
Server_MatchJoinFail,
|
||||
Client_MatchChangeSlot,
|
||||
Client_MatchReady,
|
||||
Client_MatchLock,
|
||||
Client_MatchChangeSettings,
|
||||
Server_FellowSpectatorJoined,
|
||||
Server_FellowSpectatorLeft,
|
||||
Client_MatchStart,
|
||||
AllPlayersLoaded,
|
||||
Server_MatchStart,
|
||||
Client_MatchScoreUpdate,
|
||||
Server_MatchScoreUpdate,
|
||||
Client_MatchComplete,
|
||||
Server_MatchTransferHost,
|
||||
Client_MatchChangeMods,
|
||||
Client_MatchLoadComplete,
|
||||
Server_MatchAllPlayersLoaded,
|
||||
Client_MatchNoBeatmap,
|
||||
Client_MatchNotReady,
|
||||
Client_MatchFailed,
|
||||
Server_MatchComplete,
|
||||
Client_MatchHasBeatmap,
|
||||
Client_MatchSkipRequest,
|
||||
Server_MatchSkip,
|
||||
Server_Unauthorised,
|
||||
Client_ChannelJoin,
|
||||
Server_ChannelJoinSuccess,
|
||||
Server_ChannelInfo,
|
||||
Server_ChannelKicked,
|
||||
Server_ChannelAvailableAutojoin,
|
||||
Client_BeatmapInfoRequest,
|
||||
Server_BeatmapInfoReply,
|
||||
Client_MatchTransferHost,
|
||||
Server_SupporterGMT,
|
||||
Server_FriendsList,
|
||||
Client_FriendAdd,
|
||||
Client_FriendRemove,
|
||||
Server_ProtocolNegotiation,
|
||||
Server_MainMenuIcon,
|
||||
Client_MatchChangeTeam,
|
||||
Client_ChannelPart,
|
||||
Client_ReceiveUpdates,
|
||||
Server_Monitor,
|
||||
Server_MatchPlayerSkipped,
|
||||
Client_SetAwayMessage,
|
||||
Server_UserPanel,
|
||||
IRC_only,
|
||||
Client_UserStatsRequest,
|
||||
Server_Restart,
|
||||
Client_Invite,
|
||||
Server_Invite,
|
||||
Server_ChannelInfoEnd,
|
||||
Client_MatchChangePassword,
|
||||
Server_MatchChangePassword,
|
||||
Server_SilenceEnd,
|
||||
Client_SpecialMatchInfoRequest,
|
||||
Server_UserSilenced,
|
||||
Server_UserPresenceSingle,
|
||||
Server_UserPresenceBundle,
|
||||
Client_UserPresenceRequest,
|
||||
Client_UserPresenceRequestAll,
|
||||
Client_UserToggleBlockNonFriendPM,
|
||||
Server_UserPMBlocked,
|
||||
Server_TargetIsSilenced,
|
||||
Server_VersionUpdateForced,
|
||||
Server_SwitchServer,
|
||||
Server_AccountRestricted,
|
||||
Server_RTX,
|
||||
Client_MatchAbort,
|
||||
Server_SwitchTourneyServer,
|
||||
// NOTE: Tournament client only
|
||||
Client_SpecialJoinMatchChannel,
|
||||
// NOTE: Tournament client only
|
||||
Client_SpecialLeaveMatchChanne,
|
||||
}
|
|
@ -1,153 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
User = require("./User.js"),
|
||||
uuid = require("./util/shortUUID.js"),
|
||||
ahttp = require("./util/AsyncHttpRequest.js"),
|
||||
consoleHelper = require("../consoleHelper.js"),
|
||||
// Packets
|
||||
getUserByUsername = require("./util/getUserByUsername.js"),
|
||||
getUserByToken = require("./util/getUserByToken.js"),
|
||||
countryHelper = require("./countryHelper.js"),
|
||||
loginHelper = require("./loginHelper.js"),
|
||||
Logout = require("./Packets/Logout.js"),
|
||||
Streams = require("./Streams.js"),
|
||||
UserPresenceBundle = require("./Packets/UserPresenceBundle.js"),
|
||||
UserPresence = require("./Packets/UserPresence.js"),
|
||||
StatusUpdate = require("./Packets/StatusUpdate.js");
|
||||
|
||||
module.exports = async function(req, res, loginInfo) {
|
||||
// Get time at the start of login
|
||||
const loginStartTime = Date.now(),
|
||||
isTourneyClient = loginInfo.osuversion.includes("tourney");
|
||||
|
||||
// Check login
|
||||
const loginCheck = await loginHelper.checkLogin(loginInfo);
|
||||
if (loginCheck != null) {
|
||||
res.removeHeader('X-Powered-By');
|
||||
res.removeHeader('Date');
|
||||
res.writeHead(200, loginCheck[1]);
|
||||
return res.end(loginCheck[0]);
|
||||
}
|
||||
|
||||
// Get users IP for getting location
|
||||
// Get cloudflare requestee IP first
|
||||
let requestIP = req.get("cf-connecting-ip");
|
||||
|
||||
// Get IP of requestee since we are probably behind a reverse proxy
|
||||
if (requestIP == null)
|
||||
requestIP = req.get("X-Real-IP");
|
||||
|
||||
// Just get the requestee IP (we are not behind a reverse proxy)
|
||||
if (requestIP == null)
|
||||
requestIP = req.remote_addr;
|
||||
|
||||
// Make sure requestIP is never null
|
||||
if (requestIP == null)
|
||||
requestIP = "";
|
||||
|
||||
|
||||
let userLocationData = [], userLocation;
|
||||
// Check if it is a local or null IP
|
||||
if (requestIP.includes("192.168.") || requestIP.includes("127.0.") || requestIP == "") {
|
||||
// Set location to null island
|
||||
userLocationData.country = "XX";
|
||||
userLocation = [0, 0];
|
||||
} else {
|
||||
// Get user's location using zxq
|
||||
userLocationData = await ahttp(`http://ip.zxq.co/${requestIP}`, "json");
|
||||
userLocation = userLocationData.loc.split(",");
|
||||
}
|
||||
|
||||
// Get information about the user from the database
|
||||
const userDB = await global.DatabaseHelper.query("SELECT id FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||
|
||||
// Create a token for the client
|
||||
const newClientToken = uuid();
|
||||
|
||||
// Make sure user is not already connected, kick off if so.
|
||||
const connectedUser = getUserByUsername(loginInfo.username);
|
||||
if (connectedUser != null && !isTourneyClient && !connectedUser.isTourneyUser) {
|
||||
Logout(connectedUser);
|
||||
}
|
||||
|
||||
// Retreive the newly created user
|
||||
const NewUser = global.users.add(newClientToken, new User(userDB.id, loginInfo.username, newClientToken));
|
||||
// Set tourney client flag
|
||||
NewUser.isTourneyUser = isTourneyClient;
|
||||
|
||||
// Get user's data from the database
|
||||
NewUser.updateUserInfo();
|
||||
|
||||
try {
|
||||
// Save the user's location to their class for later use
|
||||
NewUser.location[0] = parseFloat(userLocation[0]);
|
||||
NewUser.location[1] = parseFloat(userLocation[1]);
|
||||
|
||||
// Save the country id for the same reason as above
|
||||
NewUser.countryID = countryHelper.getCountryID(userLocationData.country);
|
||||
|
||||
// We're ready to start putting together a login packet
|
||||
// Create an osu! Packet writer
|
||||
let osuPacketWriter = new osu.Bancho.Writer;
|
||||
|
||||
// The reply id is the user's id in any other case than an error in which case negative numbers are used
|
||||
osuPacketWriter.LoginReply(NewUser.id);
|
||||
// Current bancho protocol version. Defined in Binato.js
|
||||
osuPacketWriter.ProtocolNegotiation(global.protocolVersion);
|
||||
// Permission level 4 is osu!supporter
|
||||
osuPacketWriter.LoginPermissions(4);
|
||||
|
||||
// After sending the user their friends list send them the online users
|
||||
UserPresenceBundle(NewUser);
|
||||
|
||||
// Set title screen image
|
||||
//osuPacketWriter.TitleUpdate("http://puu.sh/jh7t7/20c04029ad.png|https://osu.ppy.sh/news/123912240253");
|
||||
|
||||
// Add user panel data packets
|
||||
UserPresence(NewUser, NewUser.id);
|
||||
StatusUpdate(NewUser, NewUser.id);
|
||||
|
||||
// peppy pls, why
|
||||
osuPacketWriter.ChannelListingComplete();
|
||||
|
||||
// Add user to #osu
|
||||
osuPacketWriter.ChannelJoinSuccess("#osu");
|
||||
if (!Streams.isUserInStream("#osu", NewUser.uuid))
|
||||
Streams.addUserToStream("#osu", NewUser.uuid);
|
||||
|
||||
// List all channels out to the client
|
||||
for (let i = 0; i < global.channels.length; i++) {
|
||||
osuPacketWriter.ChannelAvailable({
|
||||
channelName: global.channels[i].channelName,
|
||||
channelTopic: global.channels[i].channelTopic,
|
||||
channelUserCount: global.channels[i].channelUserCount
|
||||
});
|
||||
}
|
||||
|
||||
// Construct user's friends list
|
||||
const userFriends = await global.DatabaseHelper.query("SELECT friendsWith FROM friends WHERE user = ?", [NewUser.id]);
|
||||
let friendsArray = [];
|
||||
for (let i = 0; i < userFriends.length; i++) {
|
||||
friendsArray.push(userFriends[i].friendsWith);
|
||||
}
|
||||
// Send user's friends list
|
||||
osuPacketWriter.FriendsList(friendsArray);
|
||||
|
||||
osuPacketWriter.Announce(`Welcome back ${loginInfo.username}!`);
|
||||
|
||||
global.DatabaseHelper.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [global.users.getLength() - 1]);
|
||||
|
||||
res.removeHeader('X-Powered-By');
|
||||
res.removeHeader('Date');
|
||||
// Complete login
|
||||
res.writeHead(200, {
|
||||
"cho-token": NewUser.uuid,
|
||||
"Connection": "keep-alive",
|
||||
"Keep-Alive": "timeout=5, max=100",
|
||||
});
|
||||
res.end(osuPacketWriter.toBuffer, () => {
|
||||
consoleHelper.printBancho(`User login finished, took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
const osu = require("osu-packet"),
|
||||
aes256 = require("aes256"),
|
||||
crypto = require("crypto"),
|
||||
config = require("../config.json");
|
||||
|
||||
module.exports = {
|
||||
checkLogin: function(loginInfo) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// Check if there is any login information provided
|
||||
if (loginInfo == null) return resolve(incorrectLoginResponse());
|
||||
|
||||
const userDBData = await global.DatabaseHelper.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||
|
||||
// Make sure a user was found in the database
|
||||
if (userDBData == null) return resolve(incorrectLoginResponse());
|
||||
// Make sure the username is the same as the login info
|
||||
if (userDBData.username !== loginInfo.username) return resolve(incorrectLoginResponse());
|
||||
/*
|
||||
1: Old MD5 password
|
||||
2: Old AES password
|
||||
*/
|
||||
if (userDBData.has_old_password === 1) {
|
||||
if (userDBData.password_hash !== loginInfo.password)
|
||||
return resolve(incorrectLoginResponse());
|
||||
|
||||
return resolve(requiredPWChangeResponse());
|
||||
} else if (userDBData.has_old_password === 2) {
|
||||
if (aes256.decrypt(config.database.key, userDBData.password_hash) !== loginInfo.password)
|
||||
return resolve(resolve(incorrectLoginResponse()));
|
||||
|
||||
return resolve(requiredPWChangeResponse());
|
||||
} else {
|
||||
crypto.pbkdf2(loginInfo.password, userDBData.password_salt, config.database.pbkdf2.itterations, config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
} else {
|
||||
if (derivedKey.toString("hex") !== userDBData.password_hash)
|
||||
return resolve(incorrectLoginResponse());
|
||||
|
||||
return resolve(null); // We good
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
incorrectLoginResponse: incorrectLoginResponse
|
||||
}
|
||||
|
||||
function incorrectLoginResponse() {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.LoginReply(-1);
|
||||
return [
|
||||
osuPacketWriter.toBuffer,
|
||||
{
|
||||
'cho-protocol': global.protocolVersion,
|
||||
'Connection': 'keep-alive',
|
||||
'Keep-Alive': 'timeout=5, max=100',
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function requiredPWChangeResponse() {
|
||||
const osuPacketWriter = new osu.Bancho.Writer;
|
||||
osuPacketWriter.Announce("As part of migration to a new password system you are required to change your password. Please log in on the website and change your password.");
|
||||
osuPacketWriter.LoginReply(-1);
|
||||
return [
|
||||
osuPacketWriter.toBuffer,
|
||||
{
|
||||
'cho-protocol': global.protocolVersion,
|
||||
'Connection': 'keep-alive',
|
||||
'Keep-Alive': 'timeout=5, max=100',
|
||||
}
|
||||
];
|
||||
}
|
0
server/objects/Channel.ts
Normal file
0
server/objects/Channel.ts
Normal file
0
server/objects/User.ts
Normal file
0
server/objects/User.ts
Normal file
|
@ -1,111 +0,0 @@
|
|||
module.exports = {
|
||||
"client_changeAction":0,
|
||||
"client_sendPublicMessage":1,
|
||||
"client_logout":2,
|
||||
"client_requestStatusUpdate":3,
|
||||
"client_pong":4,
|
||||
"server_userID":5,
|
||||
"server_commandError":6,
|
||||
"server_sendMessage":7,
|
||||
"server_ping":8,
|
||||
"server_handleIRCUsernameChange":9,
|
||||
"server_handleIRCQuit":10,
|
||||
"server_userStats":11,
|
||||
"server_userLogout":12,
|
||||
"server_spectatorJoined":13,
|
||||
"server_spectatorLeft":14,
|
||||
"server_spectateFrames":15,
|
||||
"client_startSpectating":16,
|
||||
"client_stopSpectating":17,
|
||||
"client_spectateFrames":18,
|
||||
"server_versionUpdate":19,
|
||||
"client_errorReport":20,
|
||||
"client_cantSpectate":21,
|
||||
"server_spectatorCantSpectate":22,
|
||||
"server_getAttention":23,
|
||||
"server_notification":24,
|
||||
"client_sendPrivateMessage":25,
|
||||
"server_updateMatch":26,
|
||||
"server_newMatch":27,
|
||||
"server_disposeMatch":28,
|
||||
"client_partLobby":29,
|
||||
"client_joinLobby":30,
|
||||
"client_createMatch":31,
|
||||
"client_joinMatch":32,
|
||||
"client_partMatch":33,
|
||||
"server_lobbyJoin_obsolete":34,
|
||||
"server_lobbyPart_obsolete":35,
|
||||
"server_matchJoinSuccess":36,
|
||||
"server_matchJoinFail":37,
|
||||
"client_matchChangeSlot":38,
|
||||
"client_matchReady":39,
|
||||
"client_matchLock":40,
|
||||
"client_matchChangeSettings":41,
|
||||
"server_fellowSpectatorJoined":42,
|
||||
"server_fellowSpectatorLeft":43,
|
||||
"client_matchStart":44,
|
||||
"AllPlayersLoaded":45,
|
||||
"server_matchStart":46,
|
||||
"client_matchScoreUpdate":47,
|
||||
"server_matchScoreUpdate":48,
|
||||
"client_matchComplete":49,
|
||||
"server_matchTransferHost":50,
|
||||
"client_matchChangeMods":51,
|
||||
"client_matchLoadComplete":52,
|
||||
"server_matchAllPlayersLoaded":53,
|
||||
"client_matchNoBeatmap":54,
|
||||
"client_matchNotReady":55,
|
||||
"client_matchFailed":56,
|
||||
"server_matchComplete":58,
|
||||
"client_matchHasBeatmap":59,
|
||||
"client_matchSkipRequest":60,
|
||||
"server_matchSkip":61,
|
||||
"server_unauthorised":62,
|
||||
"client_channelJoin":63,
|
||||
"server_channelJoinSuccess":64,
|
||||
"server_channelInfo":65,
|
||||
"server_channelKicked":66,
|
||||
"server_channelAvailableAutojoin":67,
|
||||
"client_beatmapInfoRequest":68,
|
||||
"server_beatmapInfoReply":69,
|
||||
"client_matchTransferHost":70,
|
||||
"server_supporterGMT":71,
|
||||
"server_friendsList":72,
|
||||
"client_friendAdd":73,
|
||||
"client_friendRemove":74,
|
||||
"server_protocolVersion":75,
|
||||
"server_mainMenuIcon":76,
|
||||
"client_matchChangeTeam":77,
|
||||
"client_channelPart":78,
|
||||
"client_receiveUpdates":79,
|
||||
"server_topBotnet":80,
|
||||
"server_matchPlayerSkipped":81,
|
||||
"client_setAwayMessage":82,
|
||||
"server_userPanel":83,
|
||||
"IRC_only":84,
|
||||
"client_userStatsRequest":85,
|
||||
"server_restart":86,
|
||||
"client_invite":87,
|
||||
"server_invite":88,
|
||||
"server_channelInfoEnd":89,
|
||||
"client_matchChangePassword":90,
|
||||
"server_matchChangePassword":91,
|
||||
"server_silenceEnd":92,
|
||||
"client_specialMatchInfoRequest":93,
|
||||
"server_userSilenced":94,
|
||||
"server_userPresenceSingle":95,
|
||||
"server_userPresenceBundle":96,
|
||||
"client_userPresenceRequest":97,
|
||||
"client_userPresenceRequestAll":98,
|
||||
"client_userToggleBlockNonFriendPM":99,
|
||||
"server_userPMBlocked":100,
|
||||
"server_targetIsSilenced":101,
|
||||
"server_versionUpdateForced":102,
|
||||
"server_switchServer":103,
|
||||
"server_accountRestricted":104,
|
||||
"server_jumpscare":105,
|
||||
"client_matchAbort":106,
|
||||
"server_switchTourneyServer":107,
|
||||
"client_specialJoinMatchChannel":108,
|
||||
"client_specialLeaveMatchChannel":109
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
{
|
||||
"client_changeAction":0,
|
||||
"client_sendPublicMessage":1,
|
||||
"client_logout":2,
|
||||
"client_requestStatusUpdate":3,
|
||||
"client_pong":4,
|
||||
"server_userID":5,
|
||||
"server_commandError":6,
|
||||
"server_sendMessage":7,
|
||||
"server_ping":8,
|
||||
"server_handleIRCUsernameChange":9,
|
||||
"server_handleIRCQuit":10,
|
||||
"server_userStats":11,
|
||||
"server_userLogout":12,
|
||||
"server_spectatorJoined":13,
|
||||
"server_spectatorLeft":14,
|
||||
"server_spectateFrames":15,
|
||||
"client_startSpectating":16,
|
||||
"client_stopSpectating":17,
|
||||
"client_spectateFrames":18,
|
||||
"server_versionUpdate":19,
|
||||
"client_errorReport":20,
|
||||
"client_cantSpectate":21,
|
||||
"server_spectatorCantSpectate":22,
|
||||
"server_getAttention":23,
|
||||
"server_notification":24,
|
||||
"client_sendPrivateMessage":25,
|
||||
"server_updateMatch":26,
|
||||
"server_newMatch":27,
|
||||
"server_disposeMatch":28,
|
||||
"client_partLobby":29,
|
||||
"client_joinLobby":30,
|
||||
"client_createMatch":31,
|
||||
"client_joinMatch":32,
|
||||
"client_partMatch":33,
|
||||
"server_lobbyJoin_obsolete":34,
|
||||
"server_lobbyPart_obsolete":35,
|
||||
"server_matchJoinSuccess":36,
|
||||
"server_matchJoinFail":37,
|
||||
"client_matchChangeSlot":38,
|
||||
"client_matchReady":39,
|
||||
"client_matchLock":40,
|
||||
"client_matchChangeSettings":41,
|
||||
"server_fellowSpectatorJoined":42,
|
||||
"server_fellowSpectatorLeft":43,
|
||||
"client_matchStart":44,
|
||||
"AllPlayersLoaded":45,
|
||||
"server_matchStart":46,
|
||||
"client_matchScoreUpdate":47,
|
||||
"server_matchScoreUpdate":48,
|
||||
"client_matchComplete":49,
|
||||
"server_matchTransferHost":50,
|
||||
"client_matchChangeMods":51,
|
||||
"client_matchLoadComplete":52,
|
||||
"server_matchAllPlayersLoaded":53,
|
||||
"client_matchNoBeatmap":54,
|
||||
"client_matchNotReady":55,
|
||||
"client_matchFailed":56,
|
||||
"server_matchComplete":58,
|
||||
"client_matchHasBeatmap":59,
|
||||
"client_matchSkipRequest":60,
|
||||
"server_matchSkip":61,
|
||||
"server_unauthorised":62,
|
||||
"client_channelJoin":63,
|
||||
"server_channelJoinSuccess":64,
|
||||
"server_channelInfo":65,
|
||||
"server_channelKicked":66,
|
||||
"server_channelAvailableAutojoin":67,
|
||||
"client_beatmapInfoRequest":68,
|
||||
"server_beatmapInfoReply":69,
|
||||
"client_matchTransferHost":70,
|
||||
"server_supporterGMT":71,
|
||||
"server_friendsList":72,
|
||||
"client_friendAdd":73,
|
||||
"client_friendRemove":74,
|
||||
"server_protocolVersion":75,
|
||||
"server_mainMenuIcon":76,
|
||||
"client_matchChangeTeam":77,
|
||||
"client_channelPart":78,
|
||||
"client_receiveUpdates":79,
|
||||
"server_topBotnet":80,
|
||||
"server_matchPlayerSkipped":81,
|
||||
"client_setAwayMessage":82,
|
||||
"server_userPanel":83,
|
||||
"IRC_only":84,
|
||||
"client_userStatsRequest":85,
|
||||
"server_restart":86,
|
||||
"client_invite":87,
|
||||
"server_invite":88,
|
||||
"server_channelInfoEnd":89,
|
||||
"client_matchChangePassword":90,
|
||||
"server_matchChangePassword":91,
|
||||
"server_silenceEnd":92,
|
||||
"client_specialMatchInfoRequest":93,
|
||||
"server_userSilenced":94,
|
||||
"server_userPresenceSingle":95,
|
||||
"server_userPresenceBundle":96,
|
||||
"client_userPresenceRequest":97,
|
||||
"client_userPresenceRequestAll":98,
|
||||
"client_userToggleBlockNonFriendPM":99,
|
||||
"server_userPMBlocked":100,
|
||||
"server_targetIsSilenced":101,
|
||||
"server_versionUpdateForced":102,
|
||||
"server_switchServer":103,
|
||||
"server_accountRestricted":104,
|
||||
"server_jumpscare":105,
|
||||
"client_matchAbort":106,
|
||||
"server_switchTourneyServer":107,
|
||||
"client_specialJoinMatchChannel":108,
|
||||
"client_specialLeaveMatchChannel":109
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
const fetch = require("node-fetch");
|
||||
|
||||
const functionMap = {
|
||||
"text": async (res) => await res.text(),
|
||||
"json": async (res) => await res.json()
|
||||
};
|
||||
|
||||
module.exports = async function(url, reqType = "text") {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
resolve(functionMap[reqType](await fetch(url)));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"Text": 0,
|
||||
"JSON": 1,
|
||||
"XML": 2
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
class FunkyArray {
|
||||
constructor() {
|
||||
this.items = {};
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
|
||||
this.iterableArray = [];
|
||||
}
|
||||
|
||||
add(uuid, item, regenerate = true) {
|
||||
this.items[uuid] = item;
|
||||
|
||||
if (regenerate) {
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
this.regenerateIterableArray();
|
||||
}
|
||||
|
||||
return this.items[uuid];
|
||||
}
|
||||
|
||||
remove(uuid, regenerate = true) {
|
||||
delete this.items[uuid];
|
||||
if (regenerate) {
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
this.regenerateIterableArray();
|
||||
}
|
||||
}
|
||||
|
||||
removeFirstItem(regenerate = true) {
|
||||
delete this.items[this.itemKeys[0]];
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
if (regenerate) this.regenerateIterableArray();
|
||||
}
|
||||
|
||||
regenerateIterableArray() {
|
||||
this.iterableArray = new Array();
|
||||
for (let itemKey of this.itemKeys) {
|
||||
this.iterableArray.push(this.items[itemKey]);
|
||||
}
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
}
|
||||
|
||||
getFirstItem() {
|
||||
return this.items[this.itemKeys[0]];
|
||||
}
|
||||
|
||||
getLength() {
|
||||
return this.itemKeys.length;
|
||||
}
|
||||
|
||||
getKeyById(id) {
|
||||
return this.itemKeys[id];
|
||||
}
|
||||
|
||||
getById(id) {
|
||||
return this.items[this.itemKeys[id]];
|
||||
}
|
||||
|
||||
getByKey(key) {
|
||||
return this.items[key];
|
||||
}
|
||||
|
||||
getKeys() {
|
||||
return this.itemKeys;
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
getIterableItems() {
|
||||
return this.iterableArray;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FunkyArray;
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = function(id) {
|
||||
for (let user of global.users.getIterableItems()) {
|
||||
if (user.id == id)
|
||||
return user;
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = function(token) {
|
||||
return global.users.getByKey(token);
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = function(username) {
|
||||
for (let user of global.users.getIterableItems()) {
|
||||
if (user.username === username)
|
||||
return user;
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
module.exports = function(packet) {
|
||||
try {
|
||||
const p = packet.toString(); // Convert our buffer to a String
|
||||
const s = p.split('\n'); // Split our Login Data to Username Password Osuversion|blabla|bla
|
||||
const n = s[2].split('|'); // Split osuversion|blablabla|blablabla to a object.
|
||||
const username = s[0]; // Username ofc
|
||||
const password = s[1]; // Password ofc
|
||||
const osuversion = n[0]; // OsuVersion ofc.
|
||||
const TimeOffset = Number(n[1]); // Comeon, i dont realy have to tell you what this is.
|
||||
const clientData = n[3].split(':')[2]; // Some system information. such as MacAdress or DiskID
|
||||
|
||||
// If some data is not set OR is invailed throw errors
|
||||
if (username == undefined) throw 'UserName';
|
||||
if (password == undefined) throw 'password';
|
||||
if (osuversion == undefined) throw 'osuversion';
|
||||
if (TimeOffset == undefined) throw 'offset';
|
||||
if (clientData == undefined) throw 'clientData';
|
||||
|
||||
// Everything alright? return parsed data.
|
||||
const obj = {
|
||||
username: String(username),
|
||||
password: String(password),
|
||||
osuversion: String(osuversion),
|
||||
timeoffset: Number(TimeOffset),
|
||||
clientdata: String(clientData)
|
||||
};
|
||||
// Here is the return.
|
||||
return obj;
|
||||
} catch (ex) {
|
||||
// Else return undefined, that the login request got broke.
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
const uuid = require("uuid").v4;
|
||||
|
||||
module.exports = function() {
|
||||
return uuid().split("-").slice(0, 2).join("");
|
||||
}
|
10
tsconfig.json
Normal file
10
tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"rootDir": "./",
|
||||
"outDir": "./build",
|
||||
"strict": true
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 482px;
|
||||
height: 165px;
|
||||
}
|
||||
|
||||
hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.line {
|
||||
padding: 2px;
|
||||
font-size: 8pt;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.line0 {
|
||||
background-color: #edebfa;
|
||||
}
|
||||
|
||||
.line1 {
|
||||
background-color: #e3e1fa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|content|
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Binato</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre style="border-style:double;border-width:4px;width:376px;">
|
||||
|
||||
. o ..
|
||||
o . o o.o
|
||||
...oo
|
||||
__[]__ <b>Binato</b>
|
||||
__|_o_o_o\__ A custom osu!Bancho
|
||||
\""""""""""/
|
||||
\. .. . / <a href="https://binato.eusv.ml">Website</a> | <a href="https://github.com/tgpethan/Binato">Github</a>
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue