progress
This commit is contained in:
parent
1a871e4c35
commit
734cebb19e
48 changed files with 2881 additions and 3706 deletions
|
@ -12,12 +12,10 @@ import { ChatHistory } from "./server/ChatHistory";
|
||||||
import { Config } from "./server/interfaces/Config";
|
import { Config } from "./server/interfaces/Config";
|
||||||
import compression from "compression";
|
import compression from "compression";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { HandleRequest, GetSharedContent } from "./server/BanchoServer";
|
import { HandleRequest } from "./server/BanchoServer";
|
||||||
import { SharedContent } from "./server/interfaces/SharedContent";
|
import { Shared } from "./server/objects/Shared";
|
||||||
import { Registry, collectDefaultMetrics } from "prom-client";
|
import { Registry, collectDefaultMetrics } from "prom-client";
|
||||||
const config:Config = JSON.parse(readFileSync(__dirname + "/config.json").toString()) as Config;
|
const config:Config = JSON.parse(readFileSync(__dirname + "/config.json").toString()) as Config;
|
||||||
// Pull out shared data from BanchoServer
|
|
||||||
const sharedContent:SharedContent = GetSharedContent();
|
|
||||||
|
|
||||||
const binatoApp:express.Application = express();
|
const binatoApp:express.Application = express();
|
||||||
|
|
||||||
|
@ -55,6 +53,7 @@ binatoApp.use((req, res) => {
|
||||||
if (req.url == "/" || req.url == "/index.html" || req.url == "/index") {
|
if (req.url == "/" || req.url == "/index.html" || req.url == "/index") {
|
||||||
res.send(INDEX_PAGE);
|
res.send(INDEX_PAGE);
|
||||||
} else if (req.url == "/chat") {
|
} else if (req.url == "/chat") {
|
||||||
|
// I don't think this works??
|
||||||
res.send(ChatHistory.GenerateForWeb());
|
res.send(ChatHistory.GenerateForWeb());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import chalk from "chalk";
|
import * as dyetty from "dyetty";
|
||||||
|
|
||||||
enum LogType {
|
enum LogType {
|
||||||
INFO,
|
INFO,
|
||||||
|
@ -7,14 +7,14 @@ enum LogType {
|
||||||
};
|
};
|
||||||
|
|
||||||
const LogTags = {
|
const LogTags = {
|
||||||
INFO: chalk.bgGreen(chalk.black(" INFO ")),
|
INFO: dyetty.bgGreen(dyetty.black(" INFO ")),
|
||||||
BANCHO: chalk.bgMagenta(chalk.black(" BANCHO ")),
|
BANCHO: dyetty.bgMagenta(dyetty.black(" BANCHO ")),
|
||||||
WEBREQ: chalk.bgGreen(chalk.black(" WEBREQ ")),
|
WEBREQ: dyetty.bgGreen(dyetty.black(" WEBREQ ")),
|
||||||
CHAT: chalk.bgCyan(chalk.black(" CHAT ")),
|
CHAT: dyetty.bgCyan(dyetty.black(" CHAT ")),
|
||||||
WARN: chalk.bgYellow(chalk.black(" WARN ")),
|
WARN: dyetty.bgYellow(dyetty.black(" WARN ")),
|
||||||
ERROR: chalk.bgRed(" ERRR "),
|
ERROR: dyetty.bgRed(" ERRR "),
|
||||||
REDIS: chalk.bgRed(chalk.white(" bREDIS ")),
|
REDIS: dyetty.bgRed(dyetty.white(" bREDIS ")),
|
||||||
STREAM: chalk.bgBlue(chalk.black(" STREAM "))
|
STREAM: dyetty.bgBlue(dyetty.black(" STREAM "))
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
function correctValue(i:number) : string {
|
function correctValue(i:number) : string {
|
||||||
|
@ -24,7 +24,7 @@ function correctValue(i:number) : string {
|
||||||
|
|
||||||
function getTime() : string {
|
function getTime() : string {
|
||||||
const time = new Date();
|
const time = new Date();
|
||||||
return chalk.green(`[${correctValue(time.getHours())}:${correctValue(time.getMinutes())}:${correctValue(time.getSeconds())}]`);
|
return dyetty.green(`[${correctValue(time.getHours())}:${correctValue(time.getMinutes())}:${correctValue(time.getSeconds())}]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function log(tag:string, log:string, logType:LogType = LogType.INFO) : void {
|
function log(tag:string, log:string, logType:LogType = LogType.INFO) : void {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export abstract class Constants {
|
export abstract class Constants {
|
||||||
public static readonly DEBUG = false;
|
public static readonly DEBUG = false;
|
||||||
|
public static readonly PROTOCOL_VERSION = 19;
|
||||||
}
|
}
|
18
osuTyping.ts
Normal file
18
osuTyping.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { OsuPacketWriter } from "./server/interfaces/OsuPacketWriter";
|
||||||
|
|
||||||
|
// Dummy file
|
||||||
|
const nodeOsu = require("osu-packet");
|
||||||
|
|
||||||
|
export abstract class osu {
|
||||||
|
static Bancho = {
|
||||||
|
Writer: function() : OsuPacketWriter {
|
||||||
|
return new nodeOsu.Bancho.Writer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Client = {
|
||||||
|
Reader: function(data:any) : any {
|
||||||
|
return new nodeOsu.Client.Reader(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
4523
package-lock.json
generated
4523
package-lock.json
generated
File diff suppressed because it is too large
Load diff
35
package.json
35
package.json
|
@ -4,9 +4,13 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "Binato.ts",
|
"main": "Binato.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"dev:updateCheck": "check-outdated",
|
||||||
"dev:run": "nodemon --watch './**/*.ts' Binato.ts",
|
"dev:run": "nodemon --watch './**/*.ts' Binato.ts",
|
||||||
"pack": "webpack",
|
"build": "npm-run-all build:*",
|
||||||
"build": "tsc --build",
|
"build:smash": "ts-node ./tooling/fileSmasher.ts",
|
||||||
|
"build:build": "tsc --build",
|
||||||
|
"build:mangle": "ts-node ./tooling/mangle.ts",
|
||||||
|
"build:cleanup": "ts-node ./tooling/cleanup.ts",
|
||||||
"_clean": "tsc --build --clean"
|
"_clean": "tsc --build --clean"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
|
@ -14,26 +18,25 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"aes256": "^1.1.0",
|
"aes256": "^1.1.0",
|
||||||
"chalk": "^4.1.0",
|
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
|
"dyetty": "^1.0.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"mysql2": "^2.3.3",
|
"mysql2": "^3.6.0",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.13",
|
||||||
"osu-packet": "^4.1.2",
|
"osu-packet": "^4.1.2",
|
||||||
"prom-client": "^14.1.0",
|
"prom-client": "^14.2.0",
|
||||||
"redis": "^4.5.0"
|
"redis": "^4.6.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.17",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^20.5.1",
|
||||||
"@types/node-fetch": "^2.6.2",
|
"@types/node-fetch": "^2.6.4",
|
||||||
"nodemon": "^2.0.20",
|
"check-outdated": "^2.12.0",
|
||||||
"ts-loader": "^9.4.1",
|
"nodemon": "^3.0.1",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"ts-loader": "^9.4.4",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^5.1.6"
|
||||||
"webpack": "^5.75.0",
|
|
||||||
"webpack-cli": "^4.10.0",
|
|
||||||
"webpack-node-externals": "^3.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +1,22 @@
|
||||||
import { Config } from "./interfaces/Config";
|
|
||||||
import { ConsoleHelper } from "../ConsoleHelper";
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
import { Channel } from "./objects/Channel";
|
import { Channel } from "./objects/Channel";
|
||||||
import { ChatManager } from "./ChatManager";
|
|
||||||
import { Database } from "./objects/Database";
|
|
||||||
import { DataStreamArray } from "./objects/DataStreamArray";
|
|
||||||
import { LatLng } from "./objects/LatLng";
|
import { LatLng } from "./objects/LatLng";
|
||||||
import { LoginProcess } from "./LoginProcess";
|
import { LoginProcess } from "./LoginProcess";
|
||||||
import { Packets } from "./enums/Packets";
|
import { Packets } from "./enums/Packets";
|
||||||
import { replaceAll } from "./Util";
|
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import { RedisClientType, createClient } from "redis";
|
import { RedisClientType, createClient } from "redis";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { SpectatorManager } from "./SpectatorManager";
|
import { SpectatorManager } from "./SpectatorManager";
|
||||||
import { UserArray } from "./objects/UserArray";
|
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
import { MultiplayerManager } from "./MultiplayerManager";
|
import { PrivateMessage } from "./packets/PrivateMessage";
|
||||||
import { SharedContent } from "./interfaces/SharedContent";
|
import { MessageData } from "./interfaces/MessageData";
|
||||||
const config:Config = JSON.parse(readFileSync("./config.json").toString()) as Config;
|
import { Shared } from "./objects/Shared";
|
||||||
// TODO: Port osu-packet to TypeScript
|
|
||||||
const osu = require("osu-packet");
|
|
||||||
|
|
||||||
const sharedContent:any = {};
|
const shared:Shared = new Shared();
|
||||||
// NOTE: This function should only be used externaly in Binato.ts and in this file.
|
shared.database.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
|
||||||
export function GetSharedContent() : SharedContent {
|
shared.database.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
|
||||||
return sharedContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DB:Database = sharedContent.database = new Database(config.database.address, config.database.port, config.database.username, config.database.password, config.database.name, async () => {
|
// Server Setup
|
||||||
// Close any unclosed db matches on startup
|
const spectatorManager:SpectatorManager = new SpectatorManager(shared);
|
||||||
DB.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
|
|
||||||
DB.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
|
|
||||||
});
|
|
||||||
|
|
||||||
// User session storage
|
|
||||||
const users:UserArray = sharedContent.users = new UserArray();
|
|
||||||
|
|
||||||
// Add the bot user
|
|
||||||
const botUser:User = users.add("bot", new User(3, "SillyBot", "bot", GetSharedContent()));
|
|
||||||
// Set the bot's position on the map
|
|
||||||
botUser.location = new LatLng(50, -32);
|
|
||||||
|
|
||||||
// DataStream storage
|
|
||||||
const streams:DataStreamArray = sharedContent.streams = new DataStreamArray();
|
|
||||||
|
|
||||||
// ChatManager
|
|
||||||
const chatManager:ChatManager = sharedContent.chatManager = new ChatManager(GetSharedContent());
|
|
||||||
chatManager.AddChatChannel("osu", "The main channel", true);
|
|
||||||
chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
|
|
||||||
chatManager.AddChatChannel("english", "Talk in exclusively English");
|
|
||||||
chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese");
|
|
||||||
|
|
||||||
const multiplayerManager:MultiplayerManager = sharedContent.mutiplayerManager = new MultiplayerManager(GetSharedContent());
|
|
||||||
|
|
||||||
const spectatorManager:SpectatorManager = new SpectatorManager(GetSharedContent());
|
|
||||||
|
|
||||||
let redisClient:RedisClientType;
|
let redisClient:RedisClientType;
|
||||||
|
|
||||||
|
@ -65,10 +29,10 @@ async function subscribeToChannel(channelName:string, callback:(message:string)
|
||||||
ConsoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
ConsoleHelper.printRedis(`Subscribed to ${channelName} channel`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.redis.enabled) {
|
if (shared.config.redis.enabled) {
|
||||||
(async () => {
|
(async () => {
|
||||||
redisClient = createClient({
|
redisClient = createClient({
|
||||||
url: `redis://${replaceAll(config.redis.password, " ", "") == "" ? "" : `${config.redis.password}@`}${config.redis.address}:${config.redis.port}/${config.redis.database}`
|
url: `redis://${shared.config.redis.password.replaceAll(" ", "") == "" ? "" : `${shared.config.redis.password}@`}${shared.config.redis.address}:${shared.config.redis.port}/${shared.config.redis.database}`
|
||||||
});
|
});
|
||||||
|
|
||||||
redisClient.on('error', e => ConsoleHelper.printRedis(e));
|
redisClient.on('error', e => ConsoleHelper.printRedis(e));
|
||||||
|
@ -79,15 +43,13 @@ if (config.redis.enabled) {
|
||||||
|
|
||||||
// Score submit update channel
|
// Score submit update channel
|
||||||
subscribeToChannel("binato:update_user_stats", (message) => {
|
subscribeToChannel("binato:update_user_stats", (message) => {
|
||||||
if (typeof(message) === "string") {
|
const user = shared.users.getById(parseInt(message));
|
||||||
const user = users.getById(parseInt(message));
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
// Update user info
|
// Update user info
|
||||||
user.updateUserInfo(true);
|
user.updateUserInfo(true);
|
||||||
|
|
||||||
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
ConsoleHelper.printRedis(`Score submission stats update request received for ${user.username}`);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
} else ConsoleHelper.printWarn("Redis is disabled!");
|
} else ConsoleHelper.printWarn("Redis is disabled!");
|
||||||
|
@ -98,10 +60,18 @@ import { Logout } from "./packets/Logout";
|
||||||
import { UserPresence } from "./packets/UserPresence";
|
import { UserPresence } from "./packets/UserPresence";
|
||||||
import { UserStatsRequest } from "./packets/UserStatsRequest";
|
import { UserStatsRequest } from "./packets/UserStatsRequest";
|
||||||
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
||||||
|
import { TourneyMatchSpecialInfo } from "./packets/TourneyMatchSpecialInfo";
|
||||||
|
import { osu } from "../osuTyping";
|
||||||
|
import { TourneyMatchJoinChannel } from "./packets/TourneyJoinMatchChannel";
|
||||||
|
import { TourneyMatchLeaveChannel } from "./packets/TourneyMatchLeaveChannel";
|
||||||
|
import { AddFriend } from "./packets/AddFriend";
|
||||||
|
import { RemoveFriend } from "./packets/RemoveFriend";
|
||||||
|
import { PrivateChannel } from "./objects/PrivateChannel";
|
||||||
|
import { Constants } from "../Constants";
|
||||||
|
|
||||||
// User timeout interval
|
// User timeout interval
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
for (let User of users.getIterableItems()) {
|
for (let User of shared.users.getIterableItems()) {
|
||||||
if (User.uuid == "bot") continue; // Ignore the bot
|
if (User.uuid == "bot") continue; // Ignore the bot
|
||||||
|
|
||||||
// Logout this user, they're clearly gone.
|
// Logout this user, they're clearly gone.
|
||||||
|
@ -114,29 +84,29 @@ setInterval(() => {
|
||||||
const EMPTY_BUFFER = Buffer.alloc(0);
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
|
||||||
export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
// Remove headers we don't need for Bancho
|
|
||||||
res.removeHeader('X-Powered-By');
|
|
||||||
res.removeHeader('Date');
|
|
||||||
|
|
||||||
// Get the client's token string and request data
|
// Get the client's token string and request data
|
||||||
const requestTokenString:string | undefined = req.header("osu-token");
|
const requestTokenString:string | undefined = req.header("osu-token");
|
||||||
|
|
||||||
// Check if the user is logged in
|
// Check if the user is logged in
|
||||||
if (requestTokenString == null) {
|
if (requestTokenString == null) {
|
||||||
// Only do this if we're absolutely sure that we're connected to the DB
|
// Only do this if we're absolutely sure that we're connected to the DB
|
||||||
if (DB.connected) {
|
if (shared.database.connected) {
|
||||||
// Client doesn't have a token yet, let's auth them!
|
// Client doesn't have a token yet, let's auth them!
|
||||||
|
|
||||||
await LoginProcess(req, res, packet, GetSharedContent());
|
await LoginProcess(req, res, packet, shared);
|
||||||
DB.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [users.getLength() - 1]);
|
shared.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [shared.users.getLength() - 1]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let responseData:Buffer | string = EMPTY_BUFFER;
|
let responseData:Buffer | string = EMPTY_BUFFER;
|
||||||
|
|
||||||
|
// Remove headers we don't need for Bancho
|
||||||
|
res.removeHeader('X-Powered-By');
|
||||||
|
res.removeHeader('Date'); // This is not spec compilant
|
||||||
|
|
||||||
// Client has a token, let's see what they want.
|
// Client has a token, let's see what they want.
|
||||||
try {
|
try {
|
||||||
// Get the current user
|
// Get the current user
|
||||||
const PacketUser:User | undefined = users.getByToken(requestTokenString);
|
const PacketUser:User | undefined = shared.users.getByToken(requestTokenString);
|
||||||
|
|
||||||
// Make sure the client's token isn't invalid
|
// Make sure the client's token isn't invalid
|
||||||
if (PacketUser != null) {
|
if (PacketUser != null) {
|
||||||
|
@ -144,7 +114,7 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
PacketUser.timeoutTime = Date.now() + 60000;
|
PacketUser.timeoutTime = Date.now() + 60000;
|
||||||
|
|
||||||
// Create a new osu! packet reader
|
// Create a new osu! packet reader
|
||||||
const osuPacketReader = new osu.Client.Reader(packet);
|
const osuPacketReader = osu.Client.Reader(packet);
|
||||||
// Parse current bancho packet
|
// Parse current bancho packet
|
||||||
const PacketData = osuPacketReader.Parse();
|
const PacketData = osuPacketReader.Parse();
|
||||||
|
|
||||||
|
@ -156,7 +126,8 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_SendPublicMessage:
|
case Packets.Client_SendPublicMessage:
|
||||||
let channel = chatManager.GetChannelByName(CurrentPacket.data.target);
|
const message:MessageData = CurrentPacket.data;
|
||||||
|
let channel = shared.chatManager.GetChannelByName(message.target);
|
||||||
if (channel instanceof Channel) {
|
if (channel instanceof Channel) {
|
||||||
channel.SendMessage(PacketUser, CurrentPacket.data.message);
|
channel.SendMessage(PacketUser, CurrentPacket.data.message);
|
||||||
}
|
}
|
||||||
|
@ -183,23 +154,23 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_SendPrivateMessage:
|
case Packets.Client_SendPrivateMessage:
|
||||||
//SendPrivateMessage(PacketUser, CurrentPacket.data);
|
PrivateMessage(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_JoinLobby:
|
case Packets.Client_JoinLobby:
|
||||||
multiplayerManager.JoinLobby(PacketUser);
|
shared.multiplayerManager.JoinLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_PartLobby:
|
case Packets.Client_PartLobby:
|
||||||
multiplayerManager.LeaveLobby(PacketUser);
|
shared.multiplayerManager.LeaveLobby(PacketUser);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_CreateMatch:
|
case Packets.Client_CreateMatch:
|
||||||
await multiplayerManager.CreateMatch(PacketUser, CurrentPacket.data);
|
await shared.multiplayerManager.CreateMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_JoinMatch:
|
case Packets.Client_JoinMatch:
|
||||||
multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data);
|
shared.multiplayerManager.JoinMatch(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_MatchChangeSlot:
|
case Packets.Client_MatchChangeSlot:
|
||||||
|
@ -284,11 +255,11 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_FriendAdd:
|
case Packets.Client_FriendAdd:
|
||||||
//AddFriend(PacketUser, CurrentPacket.data);
|
AddFriend(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_FriendRemove:
|
case Packets.Client_FriendRemove:
|
||||||
//RemoveFriend(PacketUser, CurrentPacket.data);
|
RemoveFriend(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_UserStatsRequest:
|
case Packets.Client_UserStatsRequest:
|
||||||
|
@ -296,15 +267,15 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_SpecialMatchInfoRequest:
|
case Packets.Client_SpecialMatchInfoRequest:
|
||||||
//TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
TourneyMatchSpecialInfo(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_SpecialJoinMatchChannel:
|
case Packets.Client_SpecialJoinMatchChannel:
|
||||||
//TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
TourneyMatchJoinChannel(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_SpecialLeaveMatchChannel:
|
case Packets.Client_SpecialLeaveMatchChannel:
|
||||||
//TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
TourneyMatchLeaveChannel(PacketUser, CurrentPacket.data);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Packets.Client_Invite:
|
case Packets.Client_Invite:
|
||||||
|
@ -328,14 +299,18 @@ export async function HandleRequest(req:Request, res:Response, packet:Buffer) {
|
||||||
PacketUser.clearQueue();
|
PacketUser.clearQueue();
|
||||||
} else {
|
} else {
|
||||||
// Only do this if we're absolutely sure that we're connected to the DB
|
// Only do this if we're absolutely sure that we're connected to the DB
|
||||||
if (DB.connected) {
|
if (shared.database.connected) {
|
||||||
// User's token is invlid, force a reconnect
|
// User's token is invlid, force a reconnect
|
||||||
ConsoleHelper.printBancho(`Forced client re-login (Token is invalid)`);
|
ConsoleHelper.printBancho(`Forced client re-login (Token is invalid)`);
|
||||||
responseData = "\u0005\u0000\u0000\u0004\u0000\u0000\u0000<30><30><EFBFBD><EFBFBD>\u0018\u0000\u0000\u0011\u0000\u0000\u0000\u000b\u000fReconnecting...";
|
responseData = "\u0005\u0000\u0000\u0004\u0000\u0000\u0000<30><30><EFBFBD><EFBFBD>\u0018\u0000\u0000\u0011\u0000\u0000\u0000\u000b\u000fReconnecting...";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
if (Constants.DEBUG) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleHelper.printError(`${e}`);
|
||||||
} finally {
|
} finally {
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"Connection": "keep-alive",
|
"Connection": "keep-alive",
|
||||||
|
|
34
server/Bot.ts
Normal file
34
server/Bot.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { ICommand } from "./interfaces/ICommand";
|
||||||
|
import { Channel } from "./objects/Channel";
|
||||||
|
import { Shared } from "./objects/Shared";
|
||||||
|
import { User } from "./objects/User";
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
import { RankingCommand } from "./commands/Ranking";
|
||||||
|
import { LockCommand } from "./commands/Lock";
|
||||||
|
import { MultiplayerCommands } from "./commands/Multiplayer";
|
||||||
|
import { HelpCommand } from "./commands/Help";
|
||||||
|
import { RollCommand } from "./commands/RollCommand";
|
||||||
|
|
||||||
|
export class Bot {
|
||||||
|
public user:User;
|
||||||
|
private commands:{ [id: string]: ICommand } = {};
|
||||||
|
|
||||||
|
public constructor(shared:Shared, botUser:User) {
|
||||||
|
this.user = botUser;
|
||||||
|
|
||||||
|
this.commands["help"] = new HelpCommand(shared, this.commands);
|
||||||
|
this.commands["ranking"] = new RankingCommand(shared);
|
||||||
|
this.commands["lock"] = new LockCommand(shared);
|
||||||
|
this.commands["mp"] = new MultiplayerCommands(shared);
|
||||||
|
this.commands["roll"] = new RollCommand(shared);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OnMessage(channel:Channel, sender:User, text:string) {
|
||||||
|
const args = text.split(" ");
|
||||||
|
const command = this.commands[`${args.shift()?.replace("!", "").toLowerCase()}`];
|
||||||
|
if (command) {
|
||||||
|
command.exec(channel, sender, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,21 +2,22 @@ import { Channel } from "./objects/Channel";
|
||||||
import { ConsoleHelper } from "../ConsoleHelper";
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
import { FunkyArray } from "./objects/FunkyArray";
|
import { FunkyArray } from "./objects/FunkyArray";
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
import { SharedContent } from "./interfaces/SharedContent";
|
import { Shared } from "./objects/Shared";
|
||||||
const osu = require("osu-packet");
|
import { osu } from "../osuTyping";
|
||||||
|
import { PrivateChannel } from "./objects/PrivateChannel";
|
||||||
|
|
||||||
export class ChatManager {
|
export class ChatManager {
|
||||||
public chatChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
public chatChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
||||||
public forceJoinChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
public forceJoinChannels:FunkyArray<Channel> = new FunkyArray<Channel>();
|
||||||
private readonly sharedContent:SharedContent;
|
private readonly shared:Shared;
|
||||||
|
|
||||||
public constructor(sharedContent:SharedContent) {
|
public constructor(shared:Shared) {
|
||||||
this.sharedContent = sharedContent;
|
this.shared = shared;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddChatChannel(name:string, description:string, forceJoin:boolean = false) : Channel {
|
public AddChatChannel(name:string, description:string, forceJoin:boolean = false) : Channel {
|
||||||
const stream = this.sharedContent.streams.CreateStream(`chat_channel:${name}`, false);
|
const stream = this.shared.streams.CreateStream(`chat_channel:${name}`, false);
|
||||||
const channel = new Channel(this.sharedContent, `#${name}`, description, stream);
|
const channel = new Channel(this.shared, `#${name}`, description, stream);
|
||||||
this.chatChannels.add(channel.name, channel);
|
this.chatChannels.add(channel.name, channel);
|
||||||
if (forceJoin) {
|
if (forceJoin) {
|
||||||
this.forceJoinChannels.add(name, channel);
|
this.forceJoinChannels.add(name, channel);
|
||||||
|
@ -26,8 +27,8 @@ export class ChatManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public AddSpecialChatChannel(name:string, streamName:string, forceJoin:boolean = false) : Channel {
|
public AddSpecialChatChannel(name:string, streamName:string, forceJoin:boolean = false) : Channel {
|
||||||
const stream = this.sharedContent.streams.CreateStream(`chat_channel:${streamName}`, false);
|
const stream = this.shared.streams.CreateStream(`chat_channel:${streamName}`, false);
|
||||||
const channel = new Channel(this.sharedContent, `#${name}`, "", stream);
|
const channel = new Channel(this.shared, `#${name}`, "", stream);
|
||||||
this.chatChannels.add(channel.name, channel);
|
this.chatChannels.add(channel.name, channel);
|
||||||
if (forceJoin) {
|
if (forceJoin) {
|
||||||
this.forceJoinChannels.add(name, channel);
|
this.forceJoinChannels.add(name, channel);
|
||||||
|
@ -51,18 +52,31 @@ export class ChatManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AddPrivateChatChannel(user0:User, user1:User) {
|
||||||
|
const stream = this.shared.streams.CreateStream(`private_channel:${user0.username},${user1.username}`, true);
|
||||||
|
const channel = new PrivateChannel(user0, user1, stream);
|
||||||
|
this.chatChannels.add(channel.name, channel);
|
||||||
|
ConsoleHelper.printChat(`Created private chat channel [${channel.name}]`);
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
public GetChannelByName(channelName:string) : Channel | undefined {
|
public GetChannelByName(channelName:string) : Channel | undefined {
|
||||||
return this.chatChannels.getByKey(channelName);
|
return this.chatChannels.getByKey(channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetPrivateChannelByName(channelName:string) : Channel | undefined {
|
||||||
|
return this.chatChannels.getByKey(channelName);
|
||||||
|
}
|
||||||
|
|
||||||
public ForceJoinChannels(user:User) {
|
public ForceJoinChannels(user:User) {
|
||||||
for (let channel of this.forceJoinChannels.getIterableItems()) {
|
for (let channel of this.forceJoinChannels.getIterableItems()) {
|
||||||
channel.Join(user);
|
channel.Join(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public SendChannelListing(user:User) {
|
public SendChannelListing(user:User) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
for (let channel of this.chatChannels.getIterableItems()) {
|
for (let channel of this.chatChannels.getIterableItems()) {
|
||||||
if (channel.isSpecial) {
|
if (channel.isSpecial) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -1,264 +1,270 @@
|
||||||
const countryCodes = {
|
enum CountryCodes {
|
||||||
LV: 132,
|
LV = 132,
|
||||||
AD: 3,
|
AD = 3,
|
||||||
LT: 130,
|
LT = 130,
|
||||||
KM: 116,
|
KM = 116,
|
||||||
QA: 182,
|
QA = 182,
|
||||||
VA: 0,
|
VA = 0,
|
||||||
PK: 173,
|
PK = 173,
|
||||||
KI: 115,
|
KI = 115,
|
||||||
SS: 0,
|
SS = 0,
|
||||||
KH: 114,
|
KH = 114,
|
||||||
NZ: 166,
|
NZ = 166,
|
||||||
TO: 215,
|
TO = 215,
|
||||||
KZ: 122,
|
KZ = 122,
|
||||||
GA: 76,
|
BW = 35,
|
||||||
BW: 35,
|
GA = 76,
|
||||||
AX: 247,
|
AX = 247,
|
||||||
GE: 79,
|
GE = 79,
|
||||||
UA: 222,
|
UA = 222,
|
||||||
CR: 50,
|
CR = 50,
|
||||||
AE: 0,
|
AE = 0,
|
||||||
NE: 157,
|
NE = 157,
|
||||||
ZA: 240,
|
ZA = 240,
|
||||||
SK: 196,
|
SK = 196,
|
||||||
BV: 34,
|
BV = 34,
|
||||||
SH: 0,
|
SH = 0,
|
||||||
PT: 179,
|
PT = 179,
|
||||||
SC: 189,
|
SC = 189,
|
||||||
CO: 49,
|
CO = 49,
|
||||||
GP: 86,
|
GP = 86,
|
||||||
GY: 93,
|
GY = 93,
|
||||||
CM: 47,
|
CM = 47,
|
||||||
TJ: 211,
|
TJ = 211,
|
||||||
AF: 5,
|
AF = 5,
|
||||||
IE: 101,
|
IE = 101,
|
||||||
AL: 8,
|
AL = 8,
|
||||||
BG: 24,
|
BG = 24,
|
||||||
JO: 110,
|
JO = 110,
|
||||||
MU: 149,
|
MU = 149,
|
||||||
PM: 0,
|
PM = 0,
|
||||||
LA: 0,
|
LA = 0,
|
||||||
IO: 104,
|
IO = 104,
|
||||||
KY: 121,
|
KY = 121,
|
||||||
SA: 187,
|
SA = 187,
|
||||||
KN: 0,
|
KN = 0,
|
||||||
OM: 167,
|
OM = 167,
|
||||||
CY: 54,
|
CY = 54,
|
||||||
BQ: 0,
|
BQ = 0,
|
||||||
BT: 33,
|
BT = 33,
|
||||||
WS: 236,
|
WS = 236,
|
||||||
ES: 67,
|
ES = 67,
|
||||||
LR: 128,
|
LR = 128,
|
||||||
RW: 186,
|
RW = 186,
|
||||||
AQ: 12,
|
AQ = 12,
|
||||||
PW: 180,
|
PW = 180,
|
||||||
JE: 250,
|
JE = 250,
|
||||||
TN: 214,
|
TN = 214,
|
||||||
ZW: 243,
|
ZW = 243,
|
||||||
JP: 111,
|
JP = 111,
|
||||||
BB: 20,
|
BB = 20,
|
||||||
VN: 233,
|
VN = 233,
|
||||||
HN: 96,
|
HN = 96,
|
||||||
KP: 0,
|
KP = 0,
|
||||||
WF: 235,
|
WF = 235,
|
||||||
EC: 62,
|
EC = 62,
|
||||||
HU: 99,
|
HU = 99,
|
||||||
GF: 80,
|
GF = 80,
|
||||||
GQ: 87,
|
GQ = 87,
|
||||||
TW: 220,
|
TW = 220,
|
||||||
MC: 135,
|
MC = 135,
|
||||||
BE: 22,
|
BE = 22,
|
||||||
PN: 176,
|
PN = 176,
|
||||||
SZ: 205,
|
SZ = 205,
|
||||||
CZ: 55,
|
CZ = 55,
|
||||||
LY: 0,
|
LY = 0,
|
||||||
IN: 103,
|
IN = 103,
|
||||||
FM: 0,
|
FM = 0,
|
||||||
PY: 181,
|
PY = 181,
|
||||||
PH: 172,
|
PH = 172,
|
||||||
MN: 142,
|
MN = 142,
|
||||||
GG: 248,
|
GG = 248,
|
||||||
CC: 39,
|
CC = 39,
|
||||||
ME: 242,
|
ME = 242,
|
||||||
DO: 60,
|
DO = 60,
|
||||||
KR: 0,
|
KR = 0,
|
||||||
PL: 174,
|
PL = 174,
|
||||||
MT: 148,
|
MT = 148,
|
||||||
MM: 141,
|
MM = 141,
|
||||||
AW: 17,
|
AW = 17,
|
||||||
MV: 150,
|
MV = 150,
|
||||||
BD: 21,
|
BD = 21,
|
||||||
NR: 164,
|
NR = 164,
|
||||||
AT: 15,
|
AT = 15,
|
||||||
GW: 92,
|
GW = 92,
|
||||||
FR: 74,
|
FR = 74,
|
||||||
LI: 126,
|
LI = 126,
|
||||||
CF: 41,
|
CF = 41,
|
||||||
DZ: 61,
|
DZ = 61,
|
||||||
MA: 134,
|
MA = 134,
|
||||||
VG: 0,
|
VG = 0,
|
||||||
NC: 156,
|
NC = 156,
|
||||||
IQ: 105,
|
IQ = 105,
|
||||||
BN: 0,
|
BN = 0,
|
||||||
BF: 23,
|
BF = 23,
|
||||||
BO: 30,
|
BO = 30,
|
||||||
GB: 77,
|
GB = 77,
|
||||||
CU: 51,
|
CU = 51,
|
||||||
LU: 131,
|
LU = 131,
|
||||||
YT: 238,
|
YT = 238,
|
||||||
NO: 162,
|
NO = 162,
|
||||||
SM: 198,
|
SM = 198,
|
||||||
GL: 83,
|
GL = 83,
|
||||||
IS: 107,
|
IS = 107,
|
||||||
AO: 11,
|
AO = 11,
|
||||||
MH: 138,
|
MH = 138,
|
||||||
SE: 191,
|
SE = 191,
|
||||||
ZM: 241,
|
ZM = 241,
|
||||||
FJ: 70,
|
FJ = 70,
|
||||||
SL: 197,
|
SL = 197,
|
||||||
CH: 43,
|
CH = 43,
|
||||||
RU: 0,
|
RU = 0,
|
||||||
CW: 0,
|
CW = 0,
|
||||||
CX: 53,
|
CX = 53,
|
||||||
TF: 208,
|
TF = 208,
|
||||||
NL: 161,
|
NL = 161,
|
||||||
AU: 16,
|
AU = 16,
|
||||||
FI: 69,
|
FI = 69,
|
||||||
MS: 147,
|
MS = 147,
|
||||||
GH: 81,
|
GH = 81,
|
||||||
BY: 36,
|
BY = 36,
|
||||||
IL: 102,
|
IL = 102,
|
||||||
VC: 0,
|
VC = 0,
|
||||||
NG: 159,
|
NG = 159,
|
||||||
HT: 98,
|
HT = 98,
|
||||||
LS: 129,
|
LS = 129,
|
||||||
MR: 146,
|
MR = 146,
|
||||||
YE: 237,
|
YE = 237,
|
||||||
MP: 144,
|
MP = 144,
|
||||||
SX: 0,
|
SX = 0,
|
||||||
RE: 183,
|
RE = 183,
|
||||||
RO: 184,
|
RO = 184,
|
||||||
NP: 163,
|
NP = 163,
|
||||||
CG: 0,
|
CG = 0,
|
||||||
FO: 73,
|
FO = 73,
|
||||||
CI: 0,
|
CI = 0,
|
||||||
TH: 210,
|
TH = 210,
|
||||||
HK: 94,
|
HK = 94,
|
||||||
TK: 212,
|
TK = 212,
|
||||||
XK: 0,
|
XK = 0,
|
||||||
DM: 59,
|
DM = 59,
|
||||||
LC: 0,
|
LC = 0,
|
||||||
ID: 100,
|
ID = 100,
|
||||||
MG: 137,
|
MG = 137,
|
||||||
JM: 109,
|
JM = 109,
|
||||||
IT: 108,
|
IT = 108,
|
||||||
CA: 38,
|
CA = 38,
|
||||||
TZ: 221,
|
TZ = 221,
|
||||||
GI: 82,
|
GI = 82,
|
||||||
KG: 113,
|
KG = 113,
|
||||||
NU: 165,
|
NU = 165,
|
||||||
TV: 219,
|
TV = 219,
|
||||||
LB: 124,
|
LB = 124,
|
||||||
SY: 0,
|
SY = 0,
|
||||||
PR: 177,
|
PR = 177,
|
||||||
NI: 160,
|
NI = 160,
|
||||||
KE: 112,
|
KE = 112,
|
||||||
MO: 0,
|
MO = 0,
|
||||||
SR: 201,
|
SR = 201,
|
||||||
VI: 0,
|
VI = 0,
|
||||||
SV: 203,
|
SV = 203,
|
||||||
HM: 0,
|
HM = 0,
|
||||||
CD: 0,
|
CD = 0,
|
||||||
BI: 26,
|
BI = 26,
|
||||||
BM: 28,
|
BM = 28,
|
||||||
MW: 151,
|
MW = 151,
|
||||||
TM: 213,
|
TM = 213,
|
||||||
GT: 90,
|
GT = 90,
|
||||||
AG: 0,
|
AG = 0,
|
||||||
UM: 0,
|
UM = 0,
|
||||||
US: 225,
|
US = 225,
|
||||||
AR: 13,
|
AR = 13,
|
||||||
DJ: 57,
|
DJ = 57,
|
||||||
KW: 120,
|
KW = 120,
|
||||||
MY: 153,
|
MY = 153,
|
||||||
FK: 71,
|
FK = 71,
|
||||||
EG: 64,
|
EG = 64,
|
||||||
BA: 0,
|
BA = 0,
|
||||||
CN: 48,
|
CN = 48,
|
||||||
GN: 85,
|
GN = 85,
|
||||||
PS: 178,
|
PS = 178,
|
||||||
SO: 200,
|
SO = 200,
|
||||||
IM: 249,
|
IM = 249,
|
||||||
GS: 0,
|
GS = 0,
|
||||||
BR: 31,
|
BR = 31,
|
||||||
GM: 84,
|
GM = 84,
|
||||||
PF: 170,
|
PF = 170,
|
||||||
PA: 168,
|
PA = 168,
|
||||||
PG: 171,
|
PG = 171,
|
||||||
BH: 25,
|
BH = 25,
|
||||||
TG: 209,
|
TG = 209,
|
||||||
GU: 91,
|
GU = 91,
|
||||||
CK: 45,
|
CK = 45,
|
||||||
MF: 252,
|
MF = 252,
|
||||||
VE: 230,
|
VE = 230,
|
||||||
CL: 46,
|
CL = 46,
|
||||||
TR: 217,
|
TR = 217,
|
||||||
UG: 223,
|
UG = 223,
|
||||||
GD: 78,
|
GD = 78,
|
||||||
TT: 218,
|
TT = 218,
|
||||||
TL: 0,
|
TL = 0,
|
||||||
MD: 0,
|
MD = 0,
|
||||||
MK: 0,
|
MK = 0,
|
||||||
ST: 202,
|
ST = 202,
|
||||||
CV: 52,
|
CV = 52,
|
||||||
MQ: 145,
|
MQ = 145,
|
||||||
GR: 88,
|
GR = 88,
|
||||||
HR: 97,
|
HR = 97,
|
||||||
BZ: 37,
|
BZ = 37,
|
||||||
UZ: 227,
|
UZ = 227,
|
||||||
DK: 58,
|
DK = 58,
|
||||||
SN: 199,
|
SN = 199,
|
||||||
ET: 68,
|
ET = 68,
|
||||||
VU: 234,
|
VU = 234,
|
||||||
ER: 66,
|
ER = 66,
|
||||||
BJ: 27,
|
BJ = 27,
|
||||||
LK: 127,
|
LK = 127,
|
||||||
NA: 155,
|
NA = 155,
|
||||||
AS: 14,
|
AS = 14,
|
||||||
SG: 192,
|
SG = 192,
|
||||||
PE: 169,
|
PE = 169,
|
||||||
IR: 0,
|
IR = 0,
|
||||||
MX: 152,
|
MX = 152,
|
||||||
TD: 207,
|
TD = 207,
|
||||||
AZ: 18,
|
AZ = 18,
|
||||||
AM: 9,
|
AM = 9,
|
||||||
BL: 0,
|
BL = 0,
|
||||||
SJ: 195,
|
SJ = 195,
|
||||||
SB: 188,
|
SB = 188,
|
||||||
NF: 158,
|
NF = 158,
|
||||||
RS: 239,
|
RS = 239,
|
||||||
DE: 56,
|
DE = 56,
|
||||||
EH: 65,
|
EH = 65,
|
||||||
EE: 63,
|
EE = 63,
|
||||||
SD: 190,
|
SD = 190,
|
||||||
ML: 140,
|
ML = 140,
|
||||||
TC: 206,
|
TC = 206,
|
||||||
MZ: 154,
|
MZ = 154,
|
||||||
BS: 32,
|
BS = 32,
|
||||||
UY: 226,
|
UY = 226,
|
||||||
SI: 194,
|
SI = 194,
|
||||||
AI: 7
|
AI = 7
|
||||||
};
|
};
|
||||||
|
|
||||||
const countryCodeKeys = Object.keys(countryCodes);
|
const keys = Object.keys(CountryCodes);
|
||||||
|
const values = Object.values(CountryCodes);
|
||||||
|
|
||||||
export function getCountryID(code:string) : number {
|
export function getCountryID(code:string) : number {
|
||||||
// Get id of a country from a 2 char code
|
// Get id of a country from a 2 char code
|
||||||
/*const upperCode:string = code.toUpperCase();
|
const upperCode:string = code.toUpperCase();
|
||||||
if (code in countryCodes) {
|
if (upperCode in CountryCodes) {
|
||||||
return countryCodes[upperCode];
|
const code = values[keys.indexOf(upperCode)];
|
||||||
}*/
|
if (typeof(code) === "string") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import { ConsoleHelper } from "../ConsoleHelper";
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
import { Database } from "./objects/Database";
|
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { getCountryID } from "./Country";
|
import { getCountryID } from "./Country";
|
||||||
import { generateSession } from "./Util";
|
import { generateSession } from "./Util";
|
||||||
|
@ -7,46 +6,21 @@ import { LatLng } from "./objects/LatLng";
|
||||||
import { LoginInfo } from "./objects/LoginInfo";
|
import { LoginInfo } from "./objects/LoginInfo";
|
||||||
import { Logout } from "./packets/Logout";
|
import { Logout } from "./packets/Logout";
|
||||||
import { pbkdf2 } from "crypto";
|
import { pbkdf2 } from "crypto";
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { UserArray } from "./objects/UserArray";
|
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
import { DataStreamArray } from "./objects/DataStreamArray";
|
|
||||||
import { ChatManager } from "./ChatManager";
|
|
||||||
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
||||||
import { UserPresence } from "./packets/UserPresence";
|
import { UserPresence } from "./packets/UserPresence";
|
||||||
import { StatusUpdate } from "./packets/StatusUpdate";
|
import { StatusUpdate } from "./packets/StatusUpdate";
|
||||||
import { SharedContent } from "./interfaces/SharedContent";
|
import { Shared } from "./objects/Shared";
|
||||||
const config:any = JSON.parse(readFileSync("./config.json").toString());
|
import { osu } from "../osuTyping";
|
||||||
|
import { IpZxqResponse } from "./interfaces/IpZxqResponse";
|
||||||
const { decrypt: aesDecrypt } = require("aes256");
|
const { decrypt: aesDecrypt } = require("aes256");
|
||||||
const osu = require("osu-packet");
|
|
||||||
|
|
||||||
function incorrectLoginResponse() {
|
const incorrectLoginResponse:Buffer = osu.Bancho.Writer().LoginReply(-1).toBuffer;
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
|
||||||
osuPacketWriter.LoginReply(-1);
|
|
||||||
return [
|
|
||||||
osuPacketWriter.toBuffer,
|
|
||||||
{
|
|
||||||
'cho-protocol': 19,
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Keep-Alive': 'timeout=5, max=100',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function requiredPWChangeResponse() {
|
const requiredPWChangeResponse:Buffer = osu.Bancho.Writer()
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
.LoginReply(-1)
|
||||||
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.");
|
.Announce("As part of migration to a new password system you are required to change your password. Please logon to the website and change your password.").toBuffer;
|
||||||
osuPacketWriter.LoginReply(-1);
|
|
||||||
return [
|
|
||||||
osuPacketWriter.toBuffer,
|
|
||||||
{
|
|
||||||
'cho-protocol': 19,
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Keep-Alive': 'timeout=5, max=100',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LoginTypes {
|
enum LoginTypes {
|
||||||
CURRENT,
|
CURRENT,
|
||||||
|
@ -54,61 +28,67 @@ enum LoginTypes {
|
||||||
OLD_AES
|
OLD_AES
|
||||||
}
|
}
|
||||||
|
|
||||||
function TestLogin(loginInfo:LoginInfo | undefined, database:Database) {
|
enum LoginResult {
|
||||||
return new Promise(async (resolve, reject) => {
|
VALID,
|
||||||
// Check if there is any login information provided
|
MIGRATION,
|
||||||
if (loginInfo == null) return resolve(incorrectLoginResponse());
|
INCORRECT,
|
||||||
|
}
|
||||||
|
|
||||||
const userDBData:any = await database.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
function TestLogin(loginInfo:LoginInfo, shared:Shared) {
|
||||||
|
return new Promise<LoginResult>(async (resolve, reject) => {
|
||||||
|
const userDBData:any = await shared.database.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||||
|
|
||||||
// Make sure a user was found in the database
|
// Make sure a user was found in the database
|
||||||
if (userDBData == null) return resolve(incorrectLoginResponse());
|
if (userDBData == null) return resolve(LoginResult.INCORRECT);
|
||||||
// Make sure the username is the same as the login info
|
// Make sure the username is the same as the login info
|
||||||
if (userDBData.username !== loginInfo.username) return resolve(incorrectLoginResponse());
|
if (userDBData.username !== loginInfo.username) return resolve(LoginResult.INCORRECT);
|
||||||
/*
|
|
||||||
1: Old MD5 password
|
console.log(userDBData.has_old_password);
|
||||||
2: Old AES password
|
|
||||||
*/
|
|
||||||
switch (userDBData.has_old_password) {
|
switch (userDBData.has_old_password) {
|
||||||
case LoginTypes.CURRENT:
|
case LoginTypes.CURRENT:
|
||||||
pbkdf2(loginInfo.password, userDBData.password_salt, config.database.pbkdf2.itterations, config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
pbkdf2(loginInfo.password, userDBData.password_salt, shared.config.database.pbkdf2.itterations, shared.config.database.pbkdf2.keylength, "sha512", (err, derivedKey) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
} else {
|
} else {
|
||||||
if (derivedKey.toString("hex") !== userDBData.password_hash)
|
if (derivedKey.toString("hex") !== userDBData.password_hash)
|
||||||
return resolve(incorrectLoginResponse());
|
return resolve(LoginResult.INCORRECT);
|
||||||
|
|
||||||
return resolve(undefined); // We good
|
return resolve(LoginResult.VALID); // We good
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case LoginTypes.OLD_AES:
|
case LoginTypes.OLD_AES:
|
||||||
if (aesDecrypt(config.database.key, userDBData.password_hash) !== loginInfo.password) {
|
console.log("OLD AES");
|
||||||
return resolve(resolve(incorrectLoginResponse()));
|
if (aesDecrypt(shared.config.database.key, userDBData.password_hash) !== loginInfo.password) {
|
||||||
|
return resolve(LoginResult.INCORRECT);
|
||||||
}
|
}
|
||||||
return resolve(requiredPWChangeResponse());
|
console.log("correct password");
|
||||||
|
return resolve(LoginResult.MIGRATION);
|
||||||
case LoginTypes.OLD_MD5:
|
case LoginTypes.OLD_MD5:
|
||||||
if (userDBData.password_hash !== loginInfo.password) {
|
if (userDBData.password_hash !== loginInfo.password) {
|
||||||
return resolve(incorrectLoginResponse());
|
return resolve(LoginResult.INCORRECT);
|
||||||
}
|
}
|
||||||
return resolve(requiredPWChangeResponse());
|
return resolve(LoginResult.MIGRATION);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function LoginProcess(req:Request, res:Response, packet:Buffer, sharedContent:SharedContent) {
|
export async function LoginProcess(req:Request, res:Response, packet:Buffer, shared:Shared) {
|
||||||
const loginInfo = LoginInfo.From(packet);
|
|
||||||
const loginStartTime = Date.now();
|
const loginStartTime = Date.now();
|
||||||
|
const loginInfo = LoginInfo.From(packet);
|
||||||
|
|
||||||
const loginCheck:any = await TestLogin(loginInfo, sharedContent.database);
|
// Send back no data if there's no loginInfo
|
||||||
if (loginCheck != null) {
|
// Somebody is doing something funky
|
||||||
res.writeHead(200, loginCheck[1]);
|
if (loginInfo === undefined) {
|
||||||
return res.end(loginCheck[0]);
|
return res.end("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loginInfo == null)
|
const loginResult:LoginResult = await TestLogin(loginInfo, shared);
|
||||||
return;
|
let osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
let newUser:User | undefined;
|
||||||
|
let friendsPresence:Buffer = Buffer.alloc(0);
|
||||||
|
|
||||||
|
if (loginResult === LoginResult.VALID && loginInfo !== undefined) {
|
||||||
ConsoleHelper.printBancho(`New client connection. [User: ${loginInfo.username}]`);
|
ConsoleHelper.printBancho(`New client connection. [User: ${loginInfo.username}]`);
|
||||||
|
|
||||||
// Get users IP for getting location
|
// Get users IP for getting location
|
||||||
|
@ -116,48 +96,49 @@ export async function LoginProcess(req:Request, res:Response, packet:Buffer, sha
|
||||||
let requestIP = req.get("cf-connecting-ip");
|
let requestIP = req.get("cf-connecting-ip");
|
||||||
|
|
||||||
// Get IP of requestee since we are probably behind a reverse proxy
|
// Get IP of requestee since we are probably behind a reverse proxy
|
||||||
if (requestIP == null)
|
if (requestIP === undefined) {
|
||||||
requestIP = req.get("X-Real-IP");
|
requestIP = req.get("X-Real-IP");
|
||||||
|
}
|
||||||
|
|
||||||
// Just get the requestee IP (we are not behind a reverse proxy)
|
// Just get the requestee IP (we are not behind a reverse proxy)
|
||||||
// if (requestIP == null)
|
// if (requestIP == null)
|
||||||
// requestIP = req.remote_addr;
|
// requestIP = req.remote_addr;
|
||||||
|
|
||||||
// Make sure requestIP is never null
|
// Make sure requestIP is never undefined
|
||||||
if (requestIP == null)
|
if (requestIP === undefined) {
|
||||||
requestIP = "";
|
requestIP = "";
|
||||||
|
}
|
||||||
|
|
||||||
let userCountryCode:string, userLocation:LatLng;
|
let userCountryCode:string, userLocation:LatLng;
|
||||||
// Check if it is a local or null IP
|
// Check if it is a local or null IP
|
||||||
if (!requestIP.includes("192.168.") && !requestIP.includes("127.0.") && requestIP != "") {
|
if (requestIP.includes("192.168.") || requestIP.includes("127.0.") || requestIP === "") {
|
||||||
// Set location to null island
|
// Set location to null island
|
||||||
userCountryCode = "XX";
|
userCountryCode = "XX";
|
||||||
userLocation = new LatLng(0, 0);
|
userLocation = new LatLng(0, 0);
|
||||||
} else {
|
} else {
|
||||||
// Get user's location using zxq
|
// Get user's location using zxq
|
||||||
const userLocationRequest = await fetch(`https://ip.zxq.co/${requestIP}`);
|
const userLocationRequest = await fetch(`https://ip.zxq.co/${requestIP}`);
|
||||||
const userLocationData:any = await userLocationRequest.json();
|
const userLocationData:IpZxqResponse = await userLocationRequest.json();
|
||||||
const userLatLng:Array<string> = userLocationData.loc.split(",");
|
const userLatLng = userLocationData.loc.split(",");
|
||||||
userCountryCode = userLocationData.country;
|
userCountryCode = userLocationData.country;
|
||||||
userLocation = new LatLng(parseFloat(userLatLng[0]), parseFloat(userLatLng[1]));
|
userLocation = new LatLng(parseFloat(userLatLng[0]), parseFloat(userLatLng[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get information about the user from the database
|
// Get information about the user from the database
|
||||||
const userDB = await sharedContent.database.query("SELECT id FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
const userDB = await shared.database.query("SELECT id FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
|
||||||
|
|
||||||
// Create a token for the client
|
// Create a token for the client
|
||||||
const newClientToken:string = await generateSession();
|
const newClientToken:string = await generateSession();
|
||||||
const isTourneyClient = loginInfo.version.includes("tourney");
|
const isTourneyClient = loginInfo.version.includes("tourney");
|
||||||
|
|
||||||
// Make sure user is not already connected, kick off if so.
|
// Make sure user is not already connected, kick off if so.
|
||||||
const connectedUser = sharedContent.users.getByUsername(loginInfo.username);
|
const connectedUser = shared.users.getByUsername(loginInfo.username);
|
||||||
if (connectedUser != null && !isTourneyClient && !connectedUser.isTourneyUser) {
|
if (connectedUser != null && !isTourneyClient && !connectedUser.isTourneyUser) {
|
||||||
Logout(connectedUser);
|
Logout(connectedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retreive the newly created user
|
// Retreive the newly created user
|
||||||
const newUser:User = sharedContent.users.add(newClientToken, new User(userDB.id, loginInfo.username, newClientToken, sharedContent));
|
newUser = shared.users.add(newClientToken, new User(userDB.id, loginInfo.username, newClientToken, shared));
|
||||||
// Set tourney client flag
|
// Set tourney client flag
|
||||||
newUser.isTourneyUser = isTourneyClient;
|
newUser.isTourneyUser = isTourneyClient;
|
||||||
newUser.location = userLocation;
|
newUser.location = userLocation;
|
||||||
|
@ -166,23 +147,16 @@ export async function LoginProcess(req:Request, res:Response, packet:Buffer, sha
|
||||||
newUser.updateUserInfo();
|
newUser.updateUserInfo();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save the country id for the same reason as above
|
|
||||||
newUser.countryID = getCountryID(userCountryCode);
|
newUser.countryID = getCountryID(userCountryCode);
|
||||||
|
|
||||||
// We're ready to start putting together a login packet
|
// We're ready to start putting together a login response
|
||||||
// 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
|
// 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);
|
osuPacketWriter.LoginReply(newUser.id);
|
||||||
// Current bancho protocol version. Defined in Binato.js
|
|
||||||
osuPacketWriter.ProtocolNegotiation(19);
|
osuPacketWriter.ProtocolNegotiation(19);
|
||||||
// Permission level 4 is osu!supporter
|
// Permission level 4 is osu!supporter
|
||||||
osuPacketWriter.LoginPermissions(4);
|
osuPacketWriter.LoginPermissions(4);
|
||||||
|
|
||||||
// After sending the user their friends list send them the online users
|
|
||||||
UserPresenceBundle(newUser);
|
|
||||||
|
|
||||||
// Set title screen image
|
// Set title screen image
|
||||||
//osuPacketWriter.TitleUpdate("http://puu.sh/jh7t7/20c04029ad.png|https://osu.ppy.sh/news/123912240253");
|
//osuPacketWriter.TitleUpdate("http://puu.sh/jh7t7/20c04029ad.png|https://osu.ppy.sh/news/123912240253");
|
||||||
|
|
||||||
|
@ -194,32 +168,70 @@ export async function LoginProcess(req:Request, res:Response, packet:Buffer, sha
|
||||||
osuPacketWriter.ChannelListingComplete();
|
osuPacketWriter.ChannelListingComplete();
|
||||||
|
|
||||||
// Setup chat
|
// Setup chat
|
||||||
sharedContent.chatManager.ForceJoinChannels(newUser);
|
shared.chatManager.ForceJoinChannels(newUser);
|
||||||
sharedContent.chatManager.SendChannelListing(newUser);
|
shared.chatManager.SendChannelListing(newUser);
|
||||||
|
|
||||||
// Construct user's friends list
|
// Construct & send user's friends list
|
||||||
const userFriends = await sharedContent.database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]);
|
const userFriends = await shared.database.query("SELECT friendsWith FROM friends WHERE user = ?", [newUser.id]);
|
||||||
const friendsArray:Array<number> = new Array<number>();
|
const friendsArray:Array<number> = new Array<number>();
|
||||||
for (let i = 0; i < userFriends.length; i++) {
|
for (let useFriend of userFriends) {
|
||||||
friendsArray.push(userFriends[i].friendsWith);
|
const friendId:number = useFriend.friendsWith;
|
||||||
|
friendsArray.push(friendId);
|
||||||
|
|
||||||
|
// Also fetch presence for friend if they are online
|
||||||
|
if (shared.users.getById(friendId) === undefined) { continue; }
|
||||||
|
|
||||||
|
const friendPresence = UserPresence(shared, friendId);
|
||||||
|
if (friendPresence === undefined) { continue; }
|
||||||
|
|
||||||
|
friendsPresence = Buffer.concat([
|
||||||
|
friendsPresence,
|
||||||
|
friendPresence
|
||||||
|
], friendsPresence.length + friendPresence.length);
|
||||||
}
|
}
|
||||||
// Send user's friends list
|
|
||||||
osuPacketWriter.FriendsList(friendsArray);
|
osuPacketWriter.FriendsList(friendsArray);
|
||||||
|
|
||||||
|
// After sending the user their friends list send them the online users
|
||||||
|
UserPresenceBundle(newUser);
|
||||||
|
|
||||||
osuPacketWriter.Announce(`Welcome back ${loginInfo.username}!`);
|
osuPacketWriter.Announce(`Welcome back ${loginInfo.username}!`);
|
||||||
|
// TODO: Remove once merged into master
|
||||||
|
osuPacketWriter.Announce("WARNING\nThis is a development test server made for the TypeScript rewrite.\nAnything could happen be it data loss, catastrophic crashes or otherwise.\nHere be dragons.");
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.removeHeader('X-Powered-By');
|
res.removeHeader('X-Powered-By');
|
||||||
res.removeHeader('Date');
|
res.removeHeader('Date');
|
||||||
// Complete login
|
// Complete / Fail login
|
||||||
|
const writerBuffer:Buffer = osuPacketWriter.toBuffer;
|
||||||
|
if (newUser === undefined) {
|
||||||
|
res.writeHead(200, {
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Keep-Alive": "timeout=5, max=100"
|
||||||
|
});
|
||||||
|
console.log(res.headersSent);
|
||||||
|
switch (loginResult) {
|
||||||
|
case LoginResult.INCORRECT:
|
||||||
|
res.end(incorrectLoginResponse, () => {
|
||||||
|
ConsoleHelper.printBancho(`User login failed (Incorrect Password) took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case LoginResult.MIGRATION:
|
||||||
|
res.end(requiredPWChangeResponse, () => {
|
||||||
|
ConsoleHelper.printBancho(`User login failed (Migration Required) took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
"cho-token": newUser.uuid,
|
"cho-token": newUser.uuid,
|
||||||
"Connection": "keep-alive",
|
"Connection": "keep-alive",
|
||||||
"Keep-Alive": "timeout=5, max=100",
|
"Keep-Alive": "timeout=5, max=100",
|
||||||
});
|
});
|
||||||
res.end(osuPacketWriter.toBuffer, () => {
|
res.end(writerBuffer, () => {
|
||||||
ConsoleHelper.printBancho(`User login finished, took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
ConsoleHelper.printBancho(`User login finished, took ${Date.now() - loginStartTime}ms. [User: ${loginInfo.username}]`);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { Channel } from "./objects/Channel";
|
import { Channel } from "./objects/Channel";
|
||||||
import { SharedContent } from "./interfaces/SharedContent";
|
import { Shared } from "./objects/Shared";
|
||||||
import { SlotStatus } from "./enums/SlotStatus";
|
import { SlotStatus } from "./enums/SlotStatus";
|
||||||
import { DataStream } from "./objects/DataStream";
|
import { DataStream } from "./objects/DataStream";
|
||||||
import { Match } from "./objects/Match";
|
import { Match } from "./objects/Match";
|
||||||
|
@ -10,18 +10,18 @@ import { UserPresenceBundle } from "./packets/UserPresenceBundle";
|
||||||
import { MatchArray } from "./objects/MatchArray";
|
import { MatchArray } from "./objects/MatchArray";
|
||||||
import { MatchJoinData } from "./interfaces/MatchJoinData";
|
import { MatchJoinData } from "./interfaces/MatchJoinData";
|
||||||
import { MatchData } from "./interfaces/MatchData";
|
import { MatchData } from "./interfaces/MatchData";
|
||||||
const osu = require("osu-packet");
|
import { osu } from "../osuTyping";
|
||||||
|
|
||||||
export class MultiplayerManager {
|
export class MultiplayerManager {
|
||||||
private readonly sharedContent:SharedContent;
|
private readonly shared:Shared;
|
||||||
private matches:MatchArray = new MatchArray();
|
private matches:MatchArray = new MatchArray();
|
||||||
private readonly lobbyStream:DataStream;
|
private readonly lobbyStream:DataStream;
|
||||||
private readonly lobbyChat:Channel;
|
private readonly lobbyChat:Channel;
|
||||||
|
|
||||||
public constructor(sharedContent:SharedContent) {
|
public constructor(shared:Shared) {
|
||||||
this.sharedContent = sharedContent;
|
this.shared = shared;
|
||||||
this.lobbyStream = sharedContent.streams.CreateStream("multiplayer:lobby", false);
|
this.lobbyStream = shared.streams.CreateStream("multiplayer:lobby", false);
|
||||||
const channel = this.sharedContent.chatManager.GetChannelByName("#lobby");
|
const channel = this.shared.chatManager.GetChannelByName("#lobby");
|
||||||
if (channel === undefined) {
|
if (channel === undefined) {
|
||||||
throw "Something has gone horribly wrong, the lobby channel does not exist!";
|
throw "Something has gone horribly wrong, the lobby channel does not exist!";
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ export class MultiplayerManager {
|
||||||
match.matchStream.AddUser(user);
|
match.matchStream.AddUser(user);
|
||||||
match.matchChatChannel.Join(user);
|
match.matchChatChannel.Join(user);
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.MatchJoinSuccess(match.generateMatchJSON());
|
osuPacketWriter.MatchJoinSuccess(match.generateMatchJSON());
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ export class MultiplayerManager {
|
||||||
|
|
||||||
this.UpdateLobbyListing();
|
this.UpdateLobbyListing();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.MatchJoinFail();
|
osuPacketWriter.MatchJoinFail();
|
||||||
|
|
||||||
|
@ -107,8 +107,8 @@ export class MultiplayerManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenerateLobbyListing(user?:User) : Buffer {
|
public GenerateLobbyListing(user?:User) : Buffer {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
let bufferToSend = UserPresenceBundle(this.sharedContent);
|
let bufferToSend = UserPresenceBundle(this.shared);
|
||||||
|
|
||||||
for (let match of this.matches.getIterableItems()) {
|
for (let match of this.matches.getIterableItems()) {
|
||||||
for (let slot of match.slots) {
|
for (let slot of match.slots) {
|
||||||
|
@ -116,8 +116,13 @@ export class MultiplayerManager {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const presenceBuffer = UserPresence(this.sharedContent, slot.player.id);
|
const presenceBuffer = UserPresence(this.shared, slot.player.id);
|
||||||
const statusBuffer = StatusUpdate(this.sharedContent, slot.player.id);
|
const statusBuffer = StatusUpdate(this.shared, slot.player.id);
|
||||||
|
|
||||||
|
if (presenceBuffer === undefined || statusBuffer === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bufferToSend = Buffer.concat([bufferToSend, presenceBuffer, statusBuffer], bufferToSend.length + presenceBuffer.length + statusBuffer.length);
|
bufferToSend = Buffer.concat([bufferToSend, presenceBuffer, statusBuffer], bufferToSend.length + presenceBuffer.length + statusBuffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,8 +139,12 @@ export class MultiplayerManager {
|
||||||
return bufferToSend;
|
return bufferToSend;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GetMatchById(id:number) : Match | undefined {
|
||||||
|
return this.matches.getById(id);
|
||||||
|
}
|
||||||
|
|
||||||
public async CreateMatch(user:User, matchData:MatchData) {
|
public async CreateMatch(user:User, matchData:MatchData) {
|
||||||
const match = await Match.createMatch(user, matchData, this.sharedContent);
|
const match = await Match.createMatch(user, matchData, this.shared);
|
||||||
this.matches.add(match.matchId.toString(), match);
|
this.matches.add(match.matchId.toString(), match);
|
||||||
this.JoinMatch(user, match.matchId);
|
this.JoinMatch(user, match.matchId);
|
||||||
}
|
}
|
||||||
|
|
41
server/PrivateChatManager.ts
Normal file
41
server/PrivateChatManager.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { Channel } from "./objects/Channel";
|
||||||
|
import { ConsoleHelper } from "../ConsoleHelper";
|
||||||
|
import { FunkyArray } from "./objects/FunkyArray";
|
||||||
|
import { User } from "./objects/User";
|
||||||
|
import { Shared } from "./objects/Shared";
|
||||||
|
import { osu } from "../osuTyping";
|
||||||
|
import { PrivateChannel } from "./objects/PrivateChannel";
|
||||||
|
|
||||||
|
export class PrivateChatManager {
|
||||||
|
public chatChannels:FunkyArray<PrivateChannel> = new FunkyArray<PrivateChannel>();
|
||||||
|
private readonly shared:Shared;
|
||||||
|
|
||||||
|
public constructor(shared:Shared) {
|
||||||
|
this.shared = shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AddChannel(user0:User, user1:User) : PrivateChannel {
|
||||||
|
const stream = this.shared.streams.CreateStream(`private_channel:${user0.username},${user1.username}`, true);
|
||||||
|
const channel = new PrivateChannel(user0, user1, stream);
|
||||||
|
this.chatChannels.add(channel.name, channel);
|
||||||
|
ConsoleHelper.printChat(`Created private chat channel [${channel.name}]`);
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoveChannel(channel:PrivateChannel | string) {
|
||||||
|
if (channel instanceof Channel) {
|
||||||
|
channel.stream.Delete();
|
||||||
|
this.chatChannels.remove(channel.stream.name);
|
||||||
|
} else {
|
||||||
|
const chatChannel = this.GetChannelByName(channel);
|
||||||
|
if (chatChannel instanceof Channel) {
|
||||||
|
chatChannel.stream.Delete();
|
||||||
|
this.chatChannels.remove(chatChannel.stream.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GetChannelByName(channelName:string) : PrivateChannel | undefined {
|
||||||
|
return this.chatChannels.getByKey(channelName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import { DataStream } from "./objects/DataStream";
|
import { DataStream } from "./objects/DataStream";
|
||||||
import { SharedContent } from "./interfaces/SharedContent";
|
import { Shared } from "./objects/Shared";
|
||||||
import { User } from "./objects/User";
|
import { User } from "./objects/User";
|
||||||
const osu = require("osu-packet");
|
import { osu } from "../osuTyping";
|
||||||
|
|
||||||
export class SpectatorManager {
|
export class SpectatorManager {
|
||||||
private sharedContent:SharedContent;
|
private shared:Shared;
|
||||||
|
|
||||||
public constructor(sharedContent:SharedContent) {
|
public constructor(shared:Shared) {
|
||||||
this.sharedContent = sharedContent;
|
this.shared = shared;
|
||||||
}
|
}
|
||||||
|
|
||||||
public startSpectating(user:User, userIdToSpectate:number) {
|
public startSpectating(user:User, userIdToSpectate:number) {
|
||||||
const userToSpectate = this.sharedContent.users.getById(userIdToSpectate);
|
const userToSpectate = this.shared.users.getById(userIdToSpectate);
|
||||||
if (userToSpectate === undefined) {
|
if (userToSpectate === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -19,20 +19,20 @@ export class SpectatorManager {
|
||||||
// Use existing or create spectator stream
|
// Use existing or create spectator stream
|
||||||
let spectateStream:DataStream;
|
let spectateStream:DataStream;
|
||||||
if (userToSpectate.spectatorStream === undefined) {
|
if (userToSpectate.spectatorStream === undefined) {
|
||||||
user.spectatorStream = spectateStream = userToSpectate.spectatorStream = this.sharedContent.streams.CreateStream(`spectator:${userToSpectate.username}`);
|
user.spectatorStream = spectateStream = userToSpectate.spectatorStream = this.shared.streams.CreateStream(`spectator:${userToSpectate.username}`);
|
||||||
} else {
|
} else {
|
||||||
user.spectatorStream = spectateStream = userToSpectate.spectatorStream;
|
user.spectatorStream = spectateStream = userToSpectate.spectatorStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.spectatingUser = userToSpectate;
|
user.spectatingUser = userToSpectate;
|
||||||
|
|
||||||
let osuPacketWriter = new osu.Bancho.Writer;
|
let osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.SpectatorJoined(user.id);
|
osuPacketWriter.SpectatorJoined(user.id);
|
||||||
|
|
||||||
userToSpectate.addActionToQueue(osuPacketWriter.toBuffer);
|
userToSpectate.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
osuPacketWriter = new osu.Bancho.Writer;
|
osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.FellowSpectatorJoined(user.id);
|
osuPacketWriter.FellowSpectatorJoined(user.id);
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ export class SpectatorManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
osuPacketWriter.SpectateFrames(spectateFrameData);
|
osuPacketWriter.SpectateFrames(spectateFrameData);
|
||||||
|
|
||||||
user.spectatorStream.Send(osuPacketWriter.toBuffer);
|
user.spectatorStream.Send(osuPacketWriter.toBuffer);
|
||||||
|
@ -58,7 +58,7 @@ export class SpectatorManager {
|
||||||
|
|
||||||
const spectatedUser = user.spectatingUser;
|
const spectatedUser = user.spectatingUser;
|
||||||
|
|
||||||
let osuPacketWriter = new osu.Bancho.Writer;
|
let osuPacketWriter = osu.Bancho.Writer();
|
||||||
osuPacketWriter.SpectatorLeft(user.id);
|
osuPacketWriter.SpectatorLeft(user.id);
|
||||||
spectatedUser.addActionToQueue(osuPacketWriter.toBuffer);
|
spectatedUser.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export class SpectatorManager {
|
||||||
user.spectatingUser = undefined;
|
user.spectatingUser = undefined;
|
||||||
|
|
||||||
if (stream.IsActive) {
|
if (stream.IsActive) {
|
||||||
osuPacketWriter = new osu.Bancho.Writer;
|
osuPacketWriter = osu.Bancho.Writer();
|
||||||
osuPacketWriter.FellowSpectatorLeft(user.id);
|
osuPacketWriter.FellowSpectatorLeft(user.id);
|
||||||
stream.Send(osuPacketWriter.toBuffer);
|
stream.Send(osuPacketWriter.toBuffer);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,12 +1,3 @@
|
||||||
// Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
|
||||||
function escapeRegExp(string:string) {
|
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function replaceAll(inputString:string, toReplace:string, toReplaceWith:string) {
|
|
||||||
return inputString.replace(`/:${escapeRegExp(toReplace)}:/g`, toReplaceWith);
|
|
||||||
}
|
|
||||||
|
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
|
|
||||||
export function generateSession() : Promise<string> {
|
export function generateSession() : Promise<string> {
|
||||||
|
|
19
server/commands/BaseCommand.ts
Normal file
19
server/commands/BaseCommand.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { ICommand } from "../interfaces/ICommand";
|
||||||
|
import { Channel } from "../objects/Channel";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export class BaseCommand implements ICommand {
|
||||||
|
public shared:Shared;
|
||||||
|
public readonly helpText:string = "No help page was found for that command";
|
||||||
|
public readonly helpDescription:string = "Command has no description set";
|
||||||
|
public readonly helpArguments:Array<string> = new Array<string>();
|
||||||
|
|
||||||
|
public constructor(shared:Shared) {
|
||||||
|
this.shared = shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
41
server/commands/Help.ts
Normal file
41
server/commands/Help.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { Channel } from "../objects/Channel";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
import { BaseCommand } from "./BaseCommand";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
|
import { ICommand } from "../interfaces/ICommand";
|
||||||
|
|
||||||
|
export class HelpCommand extends BaseCommand {
|
||||||
|
public readonly helpDescription:string = "Shows this message! :)";
|
||||||
|
|
||||||
|
private readonly commandList:{ [id:string]: ICommand };
|
||||||
|
private commandKeys:Array<string> = new Array<string>();
|
||||||
|
|
||||||
|
public constructor(shared:Shared, commands:{ [id:string]: ICommand }) {
|
||||||
|
super(shared);
|
||||||
|
this.commandList = commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||||
|
if (this.commandKeys.length === 0) {
|
||||||
|
this.commandKeys = Object.keys(this.commandList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All commands
|
||||||
|
if (args.length === 0) {
|
||||||
|
let constructedHelp = "Help:\n";
|
||||||
|
for (let key of this.commandKeys) {
|
||||||
|
constructedHelp += ` !${key} - ${this.commandList[key].helpDescription}\n`;
|
||||||
|
}
|
||||||
|
channel.SendBotMessage(constructedHelp.slice(0, constructedHelp.length - 1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command page
|
||||||
|
const commandName = args[0].toLowerCase();
|
||||||
|
if (commandName in this.commandList) {
|
||||||
|
channel.SendBotMessage(this.commandList[commandName].helpText);
|
||||||
|
} else {
|
||||||
|
channel.SendBotMessage("No help page was found for that command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
server/commands/Lock.ts
Normal file
17
server/commands/Lock.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { Channel } from "../objects/Channel";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
import { BaseCommand } from "./BaseCommand";
|
||||||
|
|
||||||
|
export class LockCommand extends BaseCommand {
|
||||||
|
public readonly helpDescription:string = "Locks/Unlocks a channel and limits conversation to mods and above.";
|
||||||
|
|
||||||
|
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||||
|
if (channel.isSpecial) {
|
||||||
|
channel.SendBotMessage("Multiplayer channels cannot be locked");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.isLocked = !channel.isLocked;
|
||||||
|
channel.SendBotMessage(`Channel is now ${channel.isLocked ? "locked" : "unlocked"}`);
|
||||||
|
}
|
||||||
|
}
|
98
server/commands/Multiplayer.ts
Normal file
98
server/commands/Multiplayer.ts
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import { Channel } from "../objects/Channel";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
import { Match } from "../objects/Match";
|
||||||
|
import { BaseCommand } from "./BaseCommand";
|
||||||
|
|
||||||
|
const helpText = `Multiplayer Subcommands:
|
||||||
|
!mp start - Starts a multiplayer match with a delay (optional)
|
||||||
|
!mp abort - Aborts the currently running round / countdown
|
||||||
|
!mp obr - Toggles Battle Royale mode`;
|
||||||
|
|
||||||
|
export class MultiplayerCommands extends BaseCommand {
|
||||||
|
public readonly helpText:string = helpText;
|
||||||
|
public readonly helpDescription:string = "Command for use in multiplayer matches.";
|
||||||
|
public readonly helpArguments:Array<string> = ["subCommand"];
|
||||||
|
|
||||||
|
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||||
|
// TODO: Determine if this check is correct
|
||||||
|
if (sender.match == undefined || channel.name != "#multiplayer") {
|
||||||
|
channel.SendBotMessage("You must be in a multiplayer match to use this command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check if sender is match host
|
||||||
|
if (!User.Equals(sender, sender.match.host)) {
|
||||||
|
channel.SendBotMessage("You must be the match host to use multiplayer commands");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.length === 0) {
|
||||||
|
channel.SendBotMessage("You must specify a sub command, use \"!help mp\" to see a list of them.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subCommand = args[0].toLowerCase();
|
||||||
|
args.shift();
|
||||||
|
|
||||||
|
switch (subCommand) {
|
||||||
|
case "start":
|
||||||
|
return mpStart(channel, sender.match, args);
|
||||||
|
|
||||||
|
case "abort":
|
||||||
|
return mpAbort(channel, sender.match);
|
||||||
|
|
||||||
|
case "obr":
|
||||||
|
return mpOBR(channel, sender.match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mpStart(channel:Channel, match:Match, args:Array<string>) {
|
||||||
|
// If no time is specified start instantly
|
||||||
|
if (args.length === 0) {
|
||||||
|
channel.SendBotMessage("Good luck, have fun!");
|
||||||
|
setTimeout(() => match.startMatch(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const countdownTime = parseInt(args[0]);
|
||||||
|
if (isNaN(countdownTime)) {
|
||||||
|
channel.SendBotMessage("Countdown time must be a valid number");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let countdownUpdates = 0;
|
||||||
|
match.countdownTime = countdownTime;
|
||||||
|
match.countdownTimer = setInterval(() => {
|
||||||
|
if (match.countdownTime <= 0) {
|
||||||
|
clearInterval(match.countdownTimer);
|
||||||
|
match.countdownTimer = undefined;
|
||||||
|
channel.SendBotMessage("Good luck, have fun!");
|
||||||
|
setTimeout(() => match.startMatch(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.countdownTime <= 5 && match.countdownTime > 0) {
|
||||||
|
channel.SendBotMessage(`Starting in ${match.countdownTime} seconds`);
|
||||||
|
} else if (match.countdownTime <= 30 ? countdownUpdates % 10 === 0 : countdownUpdates % 30 === 0) {
|
||||||
|
channel.SendBotMessage(`Starting in ${match.countdownTime} seconds`);
|
||||||
|
}
|
||||||
|
|
||||||
|
match.countdownTime--;
|
||||||
|
countdownUpdates++;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mpAbort(channel:Channel, match:Match) {
|
||||||
|
if (match.countdownTimer && match.countdownTime > 0) {
|
||||||
|
clearInterval(match.countdownTimer);
|
||||||
|
match.countdownTimer = undefined;
|
||||||
|
channel.SendBotMessage("Aborted countdown");
|
||||||
|
} else {
|
||||||
|
// TODO: Determine the correct way to abort a round
|
||||||
|
match.finishMatch();
|
||||||
|
channel.SendBotMessage("Aborted current round");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mpOBR(channel:Channel, match:Match) {
|
||||||
|
|
||||||
|
}
|
37
server/commands/Ranking.ts
Normal file
37
server/commands/Ranking.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { Channel } from "../objects/Channel";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
import { RankingModes } from "../enums/RankingModes";
|
||||||
|
import { BaseCommand } from "./BaseCommand";
|
||||||
|
|
||||||
|
const helpText = `Ranking Modes:
|
||||||
|
!ranking pp - Sets your ranking mode to pp
|
||||||
|
!ranking score - Sets your ranking mode to score
|
||||||
|
!ranking acc - Sets your ranking mode to accuracy`;
|
||||||
|
|
||||||
|
export class RankingCommand extends BaseCommand {
|
||||||
|
public readonly helpText:string = helpText;
|
||||||
|
public readonly helpDescription:string = "Sets your prefered ranking type";
|
||||||
|
|
||||||
|
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||||
|
if (args.length === 0) {
|
||||||
|
channel.SendBotMessage("You must specify a ranking mode, use \"!help ranking\" to see the options.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (args[0].toLowerCase()) {
|
||||||
|
case "pp":
|
||||||
|
sender.rankingMode = RankingModes.PP;
|
||||||
|
channel.SendBotMessage("Set ranking mode to pp.");
|
||||||
|
break;
|
||||||
|
case "score":
|
||||||
|
sender.rankingMode = RankingModes.RANKED_SCORE;
|
||||||
|
channel.SendBotMessage("Set ranking mode to score.");
|
||||||
|
break;
|
||||||
|
case "acc":
|
||||||
|
sender.rankingMode = RankingModes.AVG_ACCURACY;
|
||||||
|
channel.SendBotMessage("Set ranking mode to accuracy.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sender.updateUserInfo(true);
|
||||||
|
}
|
||||||
|
}
|
21
server/commands/RollCommand.ts
Normal file
21
server/commands/RollCommand.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { Channel } from "../objects/Channel";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
import { BaseCommand } from "./BaseCommand";
|
||||||
|
|
||||||
|
export class RollCommand extends BaseCommand {
|
||||||
|
public readonly helpDescription:string = "Roll some dice and get a random number between 1 and a number (default 100)";
|
||||||
|
public readonly helpArguments:Array<string> = ["number"];
|
||||||
|
|
||||||
|
public exec(channel:Channel, sender:User, args:Array<string>) {
|
||||||
|
let limit = 99;
|
||||||
|
if (args.length === 1) {
|
||||||
|
const userLimit = parseInt(args[0]);
|
||||||
|
if (!isNaN(userLimit)) {
|
||||||
|
limit = userLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const number = Math.round(Math.random() * limit) + 1;
|
||||||
|
channel.SendBotMessage(`${sender.username} rolls ${number} point(s)`);
|
||||||
|
}
|
||||||
|
}
|
10
server/interfaces/ICommand.ts
Normal file
10
server/interfaces/ICommand.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Channel } from "../objects/Channel";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export interface ICommand {
|
||||||
|
shared:Shared,
|
||||||
|
helpText:string,
|
||||||
|
helpDescription:string,
|
||||||
|
exec: (channel:Channel, sender:User, args:Array<string>) => void
|
||||||
|
}
|
4
server/interfaces/IpZxqResponse.ts
Normal file
4
server/interfaces/IpZxqResponse.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IpZxqResponse {
|
||||||
|
country: string,
|
||||||
|
loc: string
|
||||||
|
}
|
6
server/interfaces/MessageData.ts
Normal file
6
server/interfaces/MessageData.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface MessageData {
|
||||||
|
sendingClient: string,
|
||||||
|
message: string,
|
||||||
|
target: string,
|
||||||
|
senderId: number
|
||||||
|
}
|
65
server/interfaces/OsuPacketWriter.ts
Normal file
65
server/interfaces/OsuPacketWriter.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import { MatchData } from "./MatchData"
|
||||||
|
import { MessageData } from "./MessageData"
|
||||||
|
|
||||||
|
export interface OsuPacketWriter {
|
||||||
|
// Functions
|
||||||
|
LoginReply(data:number) : OsuPacketWriter,
|
||||||
|
CommandError() : OsuPacketWriter,
|
||||||
|
SendMessage(data:MessageData) : OsuPacketWriter,
|
||||||
|
Ping() : OsuPacketWriter,
|
||||||
|
HandleIrcChangeUsername(data:any) : OsuPacketWriter,
|
||||||
|
HandleIrcQuit() : OsuPacketWriter,
|
||||||
|
HandleOsuUpdate(data:any) : OsuPacketWriter,
|
||||||
|
HandleUserQuit(data:any) : OsuPacketWriter,
|
||||||
|
SpectatorJoined(data:any) : OsuPacketWriter,
|
||||||
|
SpectatorLeft(data:any) : OsuPacketWriter,
|
||||||
|
SpectateFrames(data:any) : OsuPacketWriter,
|
||||||
|
VersionUpdate() : OsuPacketWriter,
|
||||||
|
SpectatorCantSpectate(data:any) : OsuPacketWriter,
|
||||||
|
GetAttention() : OsuPacketWriter,
|
||||||
|
Announce(data:string) : OsuPacketWriter,
|
||||||
|
MatchUpdate(data:MatchData) : OsuPacketWriter,
|
||||||
|
MatchNew(data:any) : OsuPacketWriter,
|
||||||
|
MatchDisband(data:any) : OsuPacketWriter,
|
||||||
|
MatchJoinSuccess(data:any) : OsuPacketWriter,
|
||||||
|
MatchJoinFail() : OsuPacketWriter,
|
||||||
|
FellowSpectatorJoined(data:any) : OsuPacketWriter,
|
||||||
|
FellowSpectatorLeft(data:any) : OsuPacketWriter,
|
||||||
|
MatchStart(data:any) : OsuPacketWriter,
|
||||||
|
MatchScoreUpdate(data:any) : OsuPacketWriter,
|
||||||
|
MatchTransferHost(data:any) : OsuPacketWriter,
|
||||||
|
MatchAllPlayersLoaded() : OsuPacketWriter,
|
||||||
|
MatchPlayerFailed(data:any) : OsuPacketWriter,
|
||||||
|
MatchComplete() : OsuPacketWriter,
|
||||||
|
MatchSkip() : OsuPacketWriter,
|
||||||
|
Unauthorised() : OsuPacketWriter,
|
||||||
|
ChannelJoinSuccess(data:any) : OsuPacketWriter,
|
||||||
|
ChannelAvailable(data:any) : OsuPacketWriter,
|
||||||
|
ChannelRevoked(data:any) : OsuPacketWriter,
|
||||||
|
ChannelAvailableAutojoin(data:any) : OsuPacketWriter,
|
||||||
|
BeatmapInfoReply() : OsuPacketWriter,
|
||||||
|
LoginPermissions(data:number) : OsuPacketWriter,
|
||||||
|
FriendsList(data:Array<number>) : OsuPacketWriter,
|
||||||
|
ProtocolNegotiation(data:number) : OsuPacketWriter,
|
||||||
|
TitleUpdate(data:string) : OsuPacketWriter,
|
||||||
|
Monitor() : OsuPacketWriter,
|
||||||
|
MatchPlayerSkipped(data:any) : OsuPacketWriter,
|
||||||
|
UserPresence(data:any) : OsuPacketWriter,
|
||||||
|
Restart(data:any) : OsuPacketWriter,
|
||||||
|
Invite(data:any) : OsuPacketWriter,
|
||||||
|
ChannelListingComplete() : OsuPacketWriter,
|
||||||
|
MatchChangePassword(data:any) : OsuPacketWriter,
|
||||||
|
BanInfo(data:any) : OsuPacketWriter,
|
||||||
|
UserSilenced(data:any) : OsuPacketWriter,
|
||||||
|
UserPresenceSingle(data:any) : OsuPacketWriter,
|
||||||
|
UserPresenceBundle(data:any) : OsuPacketWriter,
|
||||||
|
UserPMBlocked(data:any) : OsuPacketWriter,
|
||||||
|
TargetIsSilenced(data:any) : OsuPacketWriter,
|
||||||
|
VersionUpdateForced() : OsuPacketWriter,
|
||||||
|
SwitchServer(data:any) : OsuPacketWriter,
|
||||||
|
AccountRestricted() : OsuPacketWriter,
|
||||||
|
RTX(data:any) : OsuPacketWriter,
|
||||||
|
SwitchTourneyServer(data:any) : OsuPacketWriter
|
||||||
|
|
||||||
|
toBuffer : Buffer
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
import { ChatManager } from "../ChatManager";
|
|
||||||
import { MultiplayerManager } from "../MultiplayerManager";
|
|
||||||
import { Database } from "../objects/Database";
|
|
||||||
import { DataStreamArray } from "../objects/DataStreamArray";
|
|
||||||
import { UserArray } from "../objects/UserArray";
|
|
||||||
|
|
||||||
export interface SharedContent {
|
|
||||||
chatManager:ChatManager,
|
|
||||||
database:Database,
|
|
||||||
mutiplayerManager:MultiplayerManager,
|
|
||||||
streams:DataStreamArray,
|
|
||||||
users:UserArray,
|
|
||||||
}
|
|
|
@ -1,61 +1,38 @@
|
||||||
import { SharedContent } from "../interfaces/SharedContent";
|
import { osu } from "../../osuTyping";
|
||||||
|
import { Bot } from "../Bot";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
import { DataStream } from "./DataStream";
|
import { DataStream } from "./DataStream";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
const osu = require("osu-packet");
|
|
||||||
|
|
||||||
export class Channel {
|
export class Channel {
|
||||||
public name:string;
|
public name:string;
|
||||||
public description:string;
|
public description:string;
|
||||||
public stream:DataStream;
|
public stream:DataStream;
|
||||||
private isLocked:boolean = false;
|
public isLocked:boolean = false;
|
||||||
private _isSpecial:boolean = false;
|
private _isSpecial:boolean = false;
|
||||||
|
|
||||||
private readonly botUser:User;
|
private readonly bot:Bot;
|
||||||
|
|
||||||
public constructor(sharedContent:SharedContent, name:string, description:string, stream:DataStream, isSpecial:boolean = false) {
|
public constructor(shared:Shared, name:string, description:string, stream:DataStream, isSpecial:boolean = false) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this._isSpecial = isSpecial;
|
this._isSpecial = isSpecial;
|
||||||
|
|
||||||
const bot = sharedContent.users.getByKey("bot");
|
this.bot = shared.bot;
|
||||||
if (!(bot instanceof User)) {
|
|
||||||
throw "Something has gone horribly wrong, the bot user doesn't exist!";
|
|
||||||
}
|
|
||||||
this.botUser = bot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get self() {
|
public get isSpecial() : boolean {
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get isSpecial() {
|
|
||||||
return this._isSpecial;
|
return this._isSpecial;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get userCount() {
|
public get userCount() : number {
|
||||||
return this.stream.userCount;
|
return this.stream.userCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendMessage(sender:User, message:string) {
|
public SendMessage(sender:User, message:string) {
|
||||||
const isBotCommand = message[0] === "!";
|
if (!this.isLocked) {
|
||||||
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
if (this.isLocked && !isBotCommand) {
|
|
||||||
return this.SendSystemMessage("This channel is currently locked", sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBotCommand) {
|
|
||||||
if (message.split(" ")[0] === "!lock") {
|
|
||||||
this.isLocked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message === "!mp start") {
|
|
||||||
this.SendBotMessage("glhf!");
|
|
||||||
sender.match?.startMatch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
|
||||||
osuPacketWriter.SendMessage({
|
osuPacketWriter.SendMessage({
|
||||||
sendingClient: sender.username,
|
sendingClient: sender.username,
|
||||||
message: message,
|
message: message,
|
||||||
|
@ -65,13 +42,20 @@ export class Channel {
|
||||||
this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender);
|
this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message[0] === "!") {
|
||||||
|
this.bot.OnMessage(this, sender, message);
|
||||||
|
} else if (this.isLocked) {
|
||||||
|
return this.SendSystemMessage("This channel is currently locked", sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SendBotMessage(message:string, sendTo?:User) {
|
public SendBotMessage(message:string, sendTo?:User) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
osuPacketWriter.SendMessage({
|
osuPacketWriter.SendMessage({
|
||||||
sendingClient: this.botUser.username,
|
sendingClient: this.bot.user.username,
|
||||||
message: message,
|
message: message,
|
||||||
target: this.name,
|
target: this.name,
|
||||||
senderId: this.botUser.id
|
senderId: this.bot.user.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sendTo instanceof User) {
|
if (sendTo instanceof User) {
|
||||||
|
@ -82,7 +66,7 @@ export class Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public SendSystemMessage(message:string, sendTo?:User) {
|
public SendSystemMessage(message:string, sendTo?:User) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
osuPacketWriter.SendMessage({
|
osuPacketWriter.SendMessage({
|
||||||
sendingClient: "System",
|
sendingClient: "System",
|
||||||
message: message,
|
message: message,
|
||||||
|
@ -99,12 +83,15 @@ export class Channel {
|
||||||
|
|
||||||
public Join(user:User) {
|
public Join(user:User) {
|
||||||
this.stream.AddUser(user);
|
this.stream.AddUser(user);
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
osuPacketWriter.ChannelJoinSuccess(this.name);
|
osuPacketWriter.ChannelJoinSuccess(this.name);
|
||||||
user.addActionToQueue(osuPacketWriter.toBuffer);
|
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Leave(user:User) {
|
public Leave(user:User) {
|
||||||
this.stream.RemoveUser(user);
|
this.stream.RemoveUser(user);
|
||||||
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
osuPacketWriter.ChannelRevoked(this.name);
|
||||||
|
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,12 +5,15 @@ import { User } from "./User";
|
||||||
import { UserArray } from "./UserArray";
|
import { UserArray } from "./UserArray";
|
||||||
import { hexlify } from "../Util";
|
import { hexlify } from "../Util";
|
||||||
|
|
||||||
|
type DeleteFunction = (dataStream:DataStream) => void;
|
||||||
|
|
||||||
export class DataStream {
|
export class DataStream {
|
||||||
private users:UserArray = new UserArray();
|
private users:UserArray = new UserArray();
|
||||||
public readonly name:string;
|
public readonly name:string;
|
||||||
private readonly parent:DataStreamArray;
|
private readonly parent:DataStreamArray;
|
||||||
private readonly removeWhenEmpty:boolean;
|
private readonly removeWhenEmpty:boolean;
|
||||||
private inactive:boolean = false;
|
private inactive:boolean = false;
|
||||||
|
public onDelete?:DeleteFunction;
|
||||||
|
|
||||||
public constructor(name:string, parent:DataStreamArray, removeWhenEmpty:boolean) {
|
public constructor(name:string, parent:DataStreamArray, removeWhenEmpty:boolean) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
@ -32,6 +35,10 @@ export class DataStream {
|
||||||
return this.users.getLength();
|
return this.users.getLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HasUser(user:User) : boolean {
|
||||||
|
return this.users.getByKey(user.uuid) !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
public AddUser(user:User) : void {
|
public AddUser(user:User) : void {
|
||||||
this.checkInactive();
|
this.checkInactive();
|
||||||
|
|
||||||
|
@ -54,6 +61,9 @@ export class DataStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Delete() {
|
public Delete() {
|
||||||
|
if (typeof(this.onDelete) === "function") {
|
||||||
|
this.onDelete(this);
|
||||||
|
}
|
||||||
this.parent.DeleteStream(this);
|
this.parent.DeleteStream(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ export class Database {
|
||||||
|
|
||||||
public connected:boolean = false;
|
public connected:boolean = false;
|
||||||
|
|
||||||
public constructor(databaseAddress:string, databasePort:number = 3306, databaseUsername:string, databasePassword:string, databaseName:string, connectedCallback:Function) {
|
public constructor(databaseAddress:string, databasePort:number = 3306, databaseUsername:string, databasePassword:string, databaseName:string) {
|
||||||
this.connectionPool = createPool({
|
this.connectionPool = createPool({
|
||||||
connectionLimit: Database.CONNECTION_LIMIT,
|
connectionLimit: Database.CONNECTION_LIMIT,
|
||||||
host: databaseAddress,
|
host: databaseAddress,
|
||||||
|
@ -17,27 +17,7 @@ export class Database {
|
||||||
database: databaseName
|
database: databaseName
|
||||||
});
|
});
|
||||||
|
|
||||||
const classCreationTime:number = Date.now();
|
ConsoleHelper.printInfo(`Connected DB connection pool. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
|
||||||
let lastQueryFinished = true;
|
|
||||||
const connectionCheckInterval = setInterval(() => {
|
|
||||||
if (lastQueryFinished) {
|
|
||||||
lastQueryFinished = false;
|
|
||||||
this.query("SELECT name FROM osu_info LIMIT 1")
|
|
||||||
.then(data => {
|
|
||||||
if (!this.connected) {
|
|
||||||
this.connected = true;
|
|
||||||
ConsoleHelper.printInfo(`Connected to database. Took ${Date.now() - classCreationTime}ms`);
|
|
||||||
clearInterval(connectionCheckInterval);
|
|
||||||
lastQueryFinished = true;
|
|
||||||
|
|
||||||
connectedCallback();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
lastQueryFinished = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public query(query = "", data?:Array<any>) {
|
public query(query = "", data?:Array<any>) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ export class LoginInfo {
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
const loginData:Array<string> = data.split("\n");
|
const loginData:Array<string> = data.split("\n");
|
||||||
const extraData:Array<string> = loginData[2].split("|");
|
const extraData:Array<string> = loginData[2].split("|");
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Channel } from "./Channel";
|
import { Channel } from "./Channel";
|
||||||
import { SharedContent } from "../interfaces/SharedContent";
|
import { Shared } from "../objects/Shared";
|
||||||
import { DataStream } from "./DataStream";
|
import { DataStream } from "./DataStream";
|
||||||
import { Slot } from "./Slot";
|
import { Slot } from "./Slot";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
|
@ -11,8 +11,7 @@ import { MatchStartSkipData } from "../interfaces/MatchStartSkipData";
|
||||||
import { Mods } from "../enums/Mods";
|
import { Mods } from "../enums/Mods";
|
||||||
import { PlayerScore } from "../interfaces/PlayerScore";
|
import { PlayerScore } from "../interfaces/PlayerScore";
|
||||||
import { MatchScoreData } from "../interfaces/MatchScoreData";
|
import { MatchScoreData } from "../interfaces/MatchScoreData";
|
||||||
|
import { osu } from "../../osuTyping";
|
||||||
const osu = require("osu-packet");
|
|
||||||
|
|
||||||
export class Match {
|
export class Match {
|
||||||
// osu! Data
|
// osu! Data
|
||||||
|
@ -44,11 +43,14 @@ export class Match {
|
||||||
|
|
||||||
public playerScores?:Array<PlayerScore>;
|
public playerScores?:Array<PlayerScore>;
|
||||||
|
|
||||||
private cachedMatchJSON:MatchData;
|
public countdownTime:number = 0;
|
||||||
private readonly sharedContent:SharedContent;
|
public countdownTimer?:NodeJS.Timeout;
|
||||||
|
|
||||||
private constructor(matchData:MatchData, sharedContent:SharedContent) {
|
private cachedMatchJSON:MatchData;
|
||||||
this.sharedContent = sharedContent;
|
private readonly shared:Shared;
|
||||||
|
|
||||||
|
private constructor(matchData:MatchData, shared:Shared) {
|
||||||
|
this.shared = shared;
|
||||||
this.matchId = matchData.matchId;
|
this.matchId = matchData.matchId;
|
||||||
|
|
||||||
this.inProgress = matchData.inProgress;
|
this.inProgress = matchData.inProgress;
|
||||||
|
@ -70,11 +72,11 @@ export class Match {
|
||||||
if (slot.playerId === -1) {
|
if (slot.playerId === -1) {
|
||||||
this.slots.push(new Slot(i, slot.status, slot.team, undefined, slot.mods));
|
this.slots.push(new Slot(i, slot.status, slot.team, undefined, slot.mods));
|
||||||
} else {
|
} else {
|
||||||
this.slots.push(new Slot(i, slot.status, slot.team, sharedContent.users.getById(slot.playerId), slot.mods));
|
this.slots.push(new Slot(i, slot.status, slot.team, shared.users.getById(slot.playerId), slot.mods));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostUser = sharedContent.users.getById(matchData.host);
|
const hostUser = shared.users.getById(matchData.host);
|
||||||
if (hostUser === undefined) {
|
if (hostUser === undefined) {
|
||||||
// NOTE: This should never be possible to hit
|
// NOTE: This should never be possible to hit
|
||||||
// since this user JUST made the match.
|
// since this user JUST made the match.
|
||||||
|
@ -90,39 +92,37 @@ export class Match {
|
||||||
|
|
||||||
this.seed = matchData.seed;
|
this.seed = matchData.seed;
|
||||||
|
|
||||||
this.matchStream = sharedContent.streams.CreateStream(`multiplayer:match_${this.matchId}`, false);
|
this.matchStream = shared.streams.CreateStream(`multiplayer:match_${this.matchId}`, false);
|
||||||
this.matchChatChannel = sharedContent.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`);
|
this.matchChatChannel = shared.chatManager.AddSpecialChatChannel("multiplayer", `mp_${this.matchId}`);
|
||||||
|
|
||||||
this.cachedMatchJSON = matchData;
|
this.cachedMatchJSON = matchData;
|
||||||
|
|
||||||
//this.playerScores = null;
|
|
||||||
|
|
||||||
//this.multiplayerExtras = null;
|
//this.multiplayerExtras = null;
|
||||||
|
|
||||||
//this.isTourneyMatch = false;
|
//this.isTourneyMatch = false;
|
||||||
//this.tourneyClientUsers = [];
|
//this.tourneyClientUsers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createMatch(matchHost:User, matchData:MatchData, sharedContent:SharedContent) : Promise<Match> {
|
public static createMatch(matchHost:User, matchData:MatchData, shared:Shared) : Promise<Match> {
|
||||||
return new Promise<Match>(async (resolve, reject) => {
|
return new Promise<Match>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
matchData.matchId = (await sharedContent.database.query(
|
matchData.matchId = (await shared.database.query(
|
||||||
"INSERT INTO mp_matches (id, name, open_time, close_time, seed) VALUES (NULL, ?, UNIX_TIMESTAMP(), NULL, ?) RETURNING id;",
|
"INSERT INTO mp_matches (id, name, open_time, close_time, seed) VALUES (NULL, ?, UNIX_TIMESTAMP(), NULL, ?) RETURNING id;",
|
||||||
[matchData.gameName, matchData.seed]
|
[matchData.gameName, matchData.seed]
|
||||||
))[0]["id"];
|
))[0]["id"];
|
||||||
|
|
||||||
const matchInstance = new Match(matchData, sharedContent);
|
const matchInstance = new Match(matchData, shared);
|
||||||
|
|
||||||
// Update the status of the current user
|
// Update the status of the current user
|
||||||
StatusUpdate(matchHost, matchHost.id);
|
StatusUpdate(matchHost, matchHost.id);
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.MatchNew(matchInstance.generateMatchJSON());
|
osuPacketWriter.MatchNew(matchInstance.generateMatchJSON());
|
||||||
|
|
||||||
matchHost.addActionToQueue(osuPacketWriter.toBuffer);
|
matchHost.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
sharedContent.mutiplayerManager.UpdateLobbyListing();
|
shared.multiplayerManager.UpdateLobbyListing();
|
||||||
|
|
||||||
resolve(matchInstance);
|
resolve(matchInstance);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -166,7 +166,7 @@ export class Match {
|
||||||
this.matchStream.RemoveUser(user);
|
this.matchStream.RemoveUser(user);
|
||||||
this.matchChatChannel.Leave(user);
|
this.matchChatChannel.Leave(user);
|
||||||
|
|
||||||
// Send this after removing the user from match streams to avoid a leave notification for self
|
// Send this after removing the user from match streams to avoid a leave notification for self?
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ export class Match {
|
||||||
this.beatmapChecksum = this.cachedMatchJSON.beatmapChecksum = matchData.beatmapChecksum;
|
this.beatmapChecksum = this.cachedMatchJSON.beatmapChecksum = matchData.beatmapChecksum;
|
||||||
|
|
||||||
if (matchData.host !== this.host.id) {
|
if (matchData.host !== this.host.id) {
|
||||||
const hostUser = this.sharedContent.users.getById(matchData.host);
|
const hostUser = this.shared.users.getById(matchData.host);
|
||||||
if (hostUser === undefined) {
|
if (hostUser === undefined) {
|
||||||
// NOTE: This should never be possible to hit
|
// NOTE: This should never be possible to hit
|
||||||
throw "Host User of match was undefined";
|
throw "Host User of match was undefined";
|
||||||
|
@ -220,22 +220,24 @@ export class Match {
|
||||||
}
|
}
|
||||||
queryData.push(this.matchId);
|
queryData.push(this.matchId);
|
||||||
|
|
||||||
await this.sharedContent.database.query(`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, queryData);
|
await this.shared.database.query(`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`, queryData);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
|
||||||
// Update the match listing in the lobby to reflect these changes
|
// Update the match listing in the lobby to reflect these changes
|
||||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sendMatchUpdate() {
|
public sendMatchUpdate() {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.MatchUpdate(this.generateMatchJSON());
|
osuPacketWriter.MatchUpdate(this.generateMatchJSON());
|
||||||
|
|
||||||
// Update all users in the match with new match information
|
// Update all users in the match with new match information
|
||||||
this.matchStream.Send(osuPacketWriter.toBuffer);
|
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
|
console.log(this.slots);
|
||||||
}
|
}
|
||||||
|
|
||||||
public moveToSlot(user:User, slotToMoveTo:number) {
|
public moveToSlot(user:User, slotToMoveTo:number) {
|
||||||
|
@ -252,7 +254,7 @@ export class Match {
|
||||||
|
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
|
||||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
public changeTeam(user:User) {
|
public changeTeam(user:User) {
|
||||||
|
@ -325,7 +327,7 @@ export class Match {
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
public missingBeatmap(user:User) {
|
public missingBeatmap(user:User) {
|
||||||
|
@ -383,7 +385,7 @@ export class Match {
|
||||||
|
|
||||||
// All players have finished playing, finish the match
|
// All players have finished playing, finish the match
|
||||||
if (allSkipped) {
|
if (allSkipped) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.MatchPlayerSkipped(user.id);
|
osuPacketWriter.MatchPlayerSkipped(user.id);
|
||||||
osuPacketWriter.MatchSkip();
|
osuPacketWriter.MatchSkip();
|
||||||
|
@ -392,7 +394,7 @@ export class Match {
|
||||||
|
|
||||||
this.matchSkippedSlots = undefined;
|
this.matchSkippedSlots = undefined;
|
||||||
} else {
|
} else {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.MatchPlayerSkipped(user.id);
|
osuPacketWriter.MatchPlayerSkipped(user.id);
|
||||||
|
|
||||||
|
@ -433,7 +435,7 @@ export class Match {
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
startMatch() {
|
startMatch() {
|
||||||
|
@ -446,6 +448,7 @@ export class Match {
|
||||||
|
|
||||||
this.matchLoadSlots = new Array<MatchStartSkipData>();
|
this.matchLoadSlots = new Array<MatchStartSkipData>();
|
||||||
// Loop through all slots in the match
|
// Loop through all slots in the match
|
||||||
|
console.log(this.slots);
|
||||||
for (let slot of this.slots) {
|
for (let slot of this.slots) {
|
||||||
// Make sure the slot has a user in it
|
// Make sure the slot has a user in it
|
||||||
if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) {
|
if (slot.player === undefined || slot.status === SlotStatus.Empty || slot.status === SlotStatus.Locked) {
|
||||||
|
@ -462,7 +465,7 @@ export class Match {
|
||||||
slot.status = 32;
|
slot.status = 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
osuPacketWriter.MatchStart(this.generateMatchJSON());
|
osuPacketWriter.MatchStart(this.generateMatchJSON());
|
||||||
|
|
||||||
|
@ -473,7 +476,7 @@ export class Match {
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
|
||||||
// Update match listing in lobby to show the game is in progress
|
// Update match listing in lobby to show the game is in progress
|
||||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||||
}
|
}
|
||||||
|
|
||||||
public matchPlayerLoaded(user:User) {
|
public matchPlayerLoaded(user:User) {
|
||||||
|
@ -494,7 +497,7 @@ export class Match {
|
||||||
|
|
||||||
// All players have loaded the beatmap, start playing.
|
// All players have loaded the beatmap, start playing.
|
||||||
if (allLoaded) {
|
if (allLoaded) {
|
||||||
let osuPacketWriter = new osu.Bancho.Writer;
|
let osuPacketWriter = osu.Bancho.Writer();
|
||||||
osuPacketWriter.MatchAllPlayersLoaded();
|
osuPacketWriter.MatchAllPlayersLoaded();
|
||||||
this.matchStream.Send(osuPacketWriter.toBuffer);
|
this.matchStream.Send(osuPacketWriter.toBuffer);
|
||||||
|
|
||||||
|
@ -563,7 +566,7 @@ export class Match {
|
||||||
this.matchLoadSlots = undefined;
|
this.matchLoadSlots = undefined;
|
||||||
this.inProgress = false;
|
this.inProgress = false;
|
||||||
|
|
||||||
let osuPacketWriter = new osu.Bancho.Writer;
|
let osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
let queryData:Array<any> = [
|
let queryData:Array<any> = [
|
||||||
this.matchId,
|
this.matchId,
|
||||||
|
@ -599,7 +602,7 @@ export class Match {
|
||||||
slot.status = SlotStatus.NotReady;
|
slot.status = SlotStatus.NotReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.sharedContent.database.query("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData);
|
await this.shared.database.query("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData);
|
||||||
|
|
||||||
osuPacketWriter.MatchComplete();
|
osuPacketWriter.MatchComplete();
|
||||||
|
|
||||||
|
@ -608,7 +611,7 @@ export class Match {
|
||||||
// Update all users in the match with new info
|
// Update all users in the match with new info
|
||||||
this.sendMatchUpdate();
|
this.sendMatchUpdate();
|
||||||
|
|
||||||
this.sharedContent.mutiplayerManager.UpdateLobbyListing();
|
this.shared.multiplayerManager.UpdateLobbyListing();
|
||||||
|
|
||||||
// TODO: Re-implement multiplayer extras
|
// TODO: Re-implement multiplayer extras
|
||||||
//if (this.multiplayerExtras != null) this.multiplayerExtras.onMatchFinished(JSON.parse(JSON.stringify(this.playerScores)));
|
//if (this.multiplayerExtras != null) this.multiplayerExtras.onMatchFinished(JSON.parse(JSON.stringify(this.playerScores)));
|
||||||
|
@ -617,7 +620,7 @@ export class Match {
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlayerScore(user:User, matchScoreData:MatchScoreData) {
|
updatePlayerScore(user:User, matchScoreData:MatchScoreData) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
if (user.matchSlot === undefined || user.matchSlot.player === undefined || this.playerScores === undefined) {
|
if (user.matchSlot === undefined || user.matchSlot.player === undefined || this.playerScores === undefined) {
|
||||||
return;
|
return;
|
||||||
|
@ -647,7 +650,7 @@ export class Match {
|
||||||
}
|
}
|
||||||
|
|
||||||
matchFailed(user:User) {
|
matchFailed(user:User) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
|
||||||
// Make sure the user is in the match in a valid slot
|
// Make sure the user is in the match in a valid slot
|
||||||
if (user.matchSlot === undefined) {
|
if (user.matchSlot === undefined) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Match } from "./Match";
|
||||||
export class MatchArray extends FunkyArray<Match> {
|
export class MatchArray extends FunkyArray<Match> {
|
||||||
public getById(id:number) : Match | undefined {
|
public getById(id:number) : Match | undefined {
|
||||||
for (let match of this.getIterableItems()) {
|
for (let match of this.getIterableItems()) {
|
||||||
if (match.matchId == id) {
|
if (match.matchId === id) {
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
61
server/objects/PrivateChannel.ts
Normal file
61
server/objects/PrivateChannel.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { osu } from "../../osuTyping";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
|
import { Channel } from "./Channel";
|
||||||
|
import { DataStream } from "./DataStream";
|
||||||
|
import { User } from "./User";
|
||||||
|
|
||||||
|
export class PrivateChannel extends Channel {
|
||||||
|
private readonly user0:User;
|
||||||
|
private readonly user1:User;
|
||||||
|
|
||||||
|
public constructor(user0:User, user1:User, stream:DataStream) {
|
||||||
|
super(user0.shared, `${user0.username}${user1.username}`, "", stream);
|
||||||
|
this.user0 = user0;
|
||||||
|
this.user1 = user1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SendMessage(sender:User, message:string) {
|
||||||
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
if (!this.stream.HasUser(this.user0)) {
|
||||||
|
this.Join(this.user0);
|
||||||
|
}
|
||||||
|
if (!this.stream.HasUser(this.user1)) {
|
||||||
|
this.Join(this.user1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target:string = this.user1.username;
|
||||||
|
if (sender.uuid === this.user1.uuid) {
|
||||||
|
target = this.user0.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
osuPacketWriter.SendMessage({
|
||||||
|
sendingClient: sender.username,
|
||||||
|
message: message,
|
||||||
|
target: target,
|
||||||
|
senderId: sender.id
|
||||||
|
});
|
||||||
|
this.stream.SendWithExclusion(osuPacketWriter.toBuffer, sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Join(user:User) {
|
||||||
|
this.stream.AddUser(user);
|
||||||
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
if (user.uuid === this.user0.uuid) {
|
||||||
|
osuPacketWriter.ChannelJoinSuccess(this.user1.username);
|
||||||
|
} else if (user.uuid === this.user1.uuid) {
|
||||||
|
osuPacketWriter.ChannelJoinSuccess(this.user0.username);
|
||||||
|
}
|
||||||
|
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Leave(user:User) {
|
||||||
|
this.stream.RemoveUser(user);
|
||||||
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
if (user.id === this.user0.id) {
|
||||||
|
osuPacketWriter.ChannelRevoked(this.user1.username);
|
||||||
|
} else if (user.id === this.user1.id) {
|
||||||
|
osuPacketWriter.ChannelRevoked(this.user0.username);
|
||||||
|
}
|
||||||
|
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
|
}
|
||||||
|
}
|
44
server/objects/Shared.ts
Normal file
44
server/objects/Shared.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { ChatManager } from "../ChatManager";
|
||||||
|
import { Config } from "../interfaces/Config";
|
||||||
|
import { Database } from "../objects/Database";
|
||||||
|
import { DataStreamArray } from "../objects/DataStreamArray";
|
||||||
|
import { MultiplayerManager } from "../MultiplayerManager";
|
||||||
|
import { PrivateChatManager } from "../PrivateChatManager";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
import { UserArray } from "../objects/UserArray";
|
||||||
|
import { User } from "./User";
|
||||||
|
import { LatLng } from "./LatLng";
|
||||||
|
import { Bot } from "../Bot";
|
||||||
|
|
||||||
|
export class Shared {
|
||||||
|
public readonly chatManager:ChatManager;
|
||||||
|
public readonly config:Config;
|
||||||
|
public readonly database:Database;
|
||||||
|
public readonly multiplayerManager:MultiplayerManager;
|
||||||
|
public readonly privateChatManager:PrivateChatManager;
|
||||||
|
public readonly streams:DataStreamArray;
|
||||||
|
public readonly users:UserArray;
|
||||||
|
public readonly bot:Bot;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.config = JSON.parse(readFileSync("./config.json").toString()) as Config;
|
||||||
|
this.database = new Database(this.config.database.address, this.config.database.port, this.config.database.username, this.config.database.password, this.config.database.name);
|
||||||
|
this.streams = new DataStreamArray();
|
||||||
|
|
||||||
|
// Add the bot user
|
||||||
|
this.users = new UserArray();
|
||||||
|
const botUser = this.users.add("bot", new User(3, "SillyBot", "bot", this));
|
||||||
|
botUser.location = new LatLng(50, -32);
|
||||||
|
this.bot = new Bot(this, botUser);
|
||||||
|
|
||||||
|
this.chatManager = new ChatManager(this);
|
||||||
|
// Setup chat channels
|
||||||
|
this.chatManager.AddChatChannel("osu", "The main channel", true);
|
||||||
|
this.chatManager.AddChatChannel("lobby", "Talk about multiplayer stuff");
|
||||||
|
this.chatManager.AddChatChannel("english", "Talk in exclusively English");
|
||||||
|
this.chatManager.AddChatChannel("japanese", "Talk in exclusively Japanese");
|
||||||
|
|
||||||
|
this.multiplayerManager = new MultiplayerManager(this);
|
||||||
|
this.privateChatManager = new PrivateChatManager(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import { RankingModes } from "../enums/RankingModes";
|
||||||
import { Match } from "./Match";
|
import { Match } from "./Match";
|
||||||
import { DataStream } from "./DataStream";
|
import { DataStream } from "./DataStream";
|
||||||
import { StatusUpdate } from "../packets/StatusUpdate";
|
import { StatusUpdate } from "../packets/StatusUpdate";
|
||||||
import { SharedContent } from "../interfaces/SharedContent";
|
import { Shared } from "../objects/Shared";
|
||||||
import { Slot } from "./Slot";
|
import { Slot } from "./Slot";
|
||||||
|
|
||||||
const rankingModes = [
|
const rankingModes = [
|
||||||
|
@ -15,7 +15,7 @@ const rankingModes = [
|
||||||
export class User {
|
export class User {
|
||||||
private static readonly EMPTY_BUFFER = Buffer.alloc(0);
|
private static readonly EMPTY_BUFFER = Buffer.alloc(0);
|
||||||
|
|
||||||
public sharedContent:SharedContent;
|
public shared:Shared;
|
||||||
|
|
||||||
public id:number;
|
public id:number;
|
||||||
public username:string;
|
public username:string;
|
||||||
|
@ -65,12 +65,12 @@ export class User {
|
||||||
return user0.uuid === user1.uuid;
|
return user0.uuid === user1.uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructor(id:number, username:string, uuid:string, sharedContent:SharedContent) {
|
public constructor(id:number, username:string, uuid:string, shared:Shared) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
|
|
||||||
this.sharedContent = sharedContent;
|
this.shared = shared;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concats new actions to the user's queue
|
// Concats new actions to the user's queue
|
||||||
|
@ -83,7 +83,7 @@ export class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the user's current action
|
// Updates the user's current action
|
||||||
updatePresence(action:any) : void {
|
updatePresence(action:any) {
|
||||||
this.actionID = action.status;
|
this.actionID = action.status;
|
||||||
this.actionText = action.statusText;
|
this.actionText = action.statusText;
|
||||||
this.beatmapChecksum = action.beatmapChecksum;
|
this.beatmapChecksum = action.beatmapChecksum;
|
||||||
|
@ -97,10 +97,10 @@ export class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the user's score information from the database and caches it
|
// Gets the user's score information from the database and caches it
|
||||||
async updateUserInfo(forceUpdate:boolean = false) : Promise<void> {
|
async updateUserInfo(forceUpdate:boolean = false) {
|
||||||
const userScoreDB = await this.sharedContent.database.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
|
const userScoreDB = await this.shared.database.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
|
||||||
const mappedRankingMode = rankingModes[this.rankingMode];
|
const mappedRankingMode = rankingModes[this.rankingMode];
|
||||||
const userRankDB = await this.sharedContent.database.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);
|
const userRankDB = await this.shared.database.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";
|
if (userScoreDB == null || userRankDB == null) throw "fuck";
|
||||||
|
|
||||||
|
|
23
server/objects/database/UserInfo.ts
Normal file
23
server/objects/database/UserInfo.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
export class UserInfo {
|
||||||
|
id:number = Number.MIN_VALUE;
|
||||||
|
username:string = "";
|
||||||
|
username_safe:string = "";
|
||||||
|
password_hash:string = "";
|
||||||
|
password_salt:string = "";
|
||||||
|
email:string = "";
|
||||||
|
country:string = "";
|
||||||
|
reg_date:Date = new Date(0);
|
||||||
|
last_login_date:Date = new Date(0);
|
||||||
|
last_played_mode:number = Number.MIN_VALUE;
|
||||||
|
online_now:boolean = false;
|
||||||
|
tags:number = Number.MIN_VALUE;
|
||||||
|
supporter:boolean = false;
|
||||||
|
web_session:string = "";
|
||||||
|
verification_needed:boolean = false;
|
||||||
|
password_change_required:boolean = false;
|
||||||
|
has_old_password:number = Number.MIN_VALUE;
|
||||||
|
password_reset_key:string = "";
|
||||||
|
away_message:string = "";
|
||||||
|
last_modified_time:Date = new Date(0);
|
||||||
|
is_deleted:boolean = false;
|
||||||
|
}
|
7
server/packets/AddFriend.ts
Normal file
7
server/packets/AddFriend.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export function AddFriend(user:User, friendId:number) {
|
||||||
|
user.shared.database.query("INSERT INTO friends (user, friendsWith) VALUES (?, ?)", [
|
||||||
|
user.id, friendId
|
||||||
|
]);
|
||||||
|
}
|
|
@ -5,7 +5,9 @@ export function ChangeAction(user:User, data:any) {
|
||||||
user.updatePresence(data);
|
user.updatePresence(data);
|
||||||
|
|
||||||
if (user.spectatorStream != null) {
|
if (user.spectatorStream != null) {
|
||||||
const statusUpdate = StatusUpdate(user.sharedContent, user.id);
|
const statusUpdate = StatusUpdate(user.shared, user.id);
|
||||||
|
if (statusUpdate instanceof Buffer) {
|
||||||
user.spectatorStream.Send(statusUpdate);
|
user.spectatorStream.Send(statusUpdate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
|
@ -8,12 +8,12 @@ export async function Logout(user:User) {
|
||||||
|
|
||||||
const logoutStartTime = Date.now();
|
const logoutStartTime = Date.now();
|
||||||
|
|
||||||
user.sharedContent.streams.RemoveUserFromAllStreams(user);
|
user.shared.streams.RemoveUserFromAllStreams(user);
|
||||||
|
|
||||||
// Remove user from user list
|
// Remove user from user list
|
||||||
user.sharedContent.users.remove(user.uuid);
|
user.shared.users.remove(user.uuid);
|
||||||
|
|
||||||
await user.sharedContent.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [user.sharedContent.users.getLength() - 1]);
|
await user.shared.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [user.shared.users.getLength() - 1]);
|
||||||
|
|
||||||
ConsoleHelper.printBancho(`User logged out, took ${Date.now() - logoutStartTime}ms. [User: ${user.username}]`);
|
ConsoleHelper.printBancho(`User logged out, took ${Date.now() - logoutStartTime}ms. [User: ${user.username}]`);
|
||||||
}
|
}
|
27
server/packets/PrivateMessage.ts
Normal file
27
server/packets/PrivateMessage.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { MessageData } from "../interfaces/MessageData";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
|
import { PrivateChannel } from "../objects/PrivateChannel";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export function PrivateMessage(user:User, message:MessageData) {
|
||||||
|
const shared:Shared = user.shared;
|
||||||
|
const sendingTo = shared.users.getByUsername(message.target);
|
||||||
|
if (!(sendingTo instanceof User)) {
|
||||||
|
console.log("Sending User invalid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let channel = shared.privateChatManager.GetChannelByName(`${user.username}${sendingTo.username}`);
|
||||||
|
if (!(channel instanceof PrivateChannel)) {
|
||||||
|
console.log("First find failed");
|
||||||
|
// Try it the other way around
|
||||||
|
channel = shared.privateChatManager.GetChannelByName(`${sendingTo.username}${user.username}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(channel instanceof PrivateChannel)) {
|
||||||
|
console.log("Second find failed, creating");
|
||||||
|
channel = shared.privateChatManager.AddChannel(user, sendingTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("sending");
|
||||||
|
channel.SendMessage(user, message.message);
|
||||||
|
}
|
7
server/packets/RemoveFriend.ts
Normal file
7
server/packets/RemoveFriend.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export function RemoveFriend(user:User, friendId:number) {
|
||||||
|
user.shared.database.query("DELETE FROM friends WHERE user = ? AND friendsWith = ? LIMIT 1", [
|
||||||
|
user.id, friendId
|
||||||
|
]);
|
||||||
|
}
|
|
@ -1,22 +1,22 @@
|
||||||
import { SharedContent } from "../interfaces/SharedContent";
|
import { Shared } from "../objects/Shared";
|
||||||
import { RankingModes } from "../enums/RankingModes";
|
import { RankingModes } from "../enums/RankingModes";
|
||||||
import { User } from "../objects/User";
|
import { User } from "../objects/User";
|
||||||
const osu = require("osu-packet");
|
import { osu } from "../../osuTyping";
|
||||||
|
|
||||||
export function StatusUpdate(arg0:User | SharedContent, id:number) {
|
export function StatusUpdate(arg0:User | Shared, id:number) {
|
||||||
if (id == 3) return; // Ignore Bot
|
if (id == 3) return; // Ignore Bot
|
||||||
|
|
||||||
// Create new osu packet writer
|
// Create new osu packet writer
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
let sharedContent:SharedContent;
|
let shared:Shared;
|
||||||
if (arg0 instanceof User) {
|
if (arg0 instanceof User) {
|
||||||
sharedContent = arg0.sharedContent;
|
shared = arg0.shared;
|
||||||
} else {
|
} else {
|
||||||
sharedContent = arg0;
|
shared = arg0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user's class
|
// Get user's class
|
||||||
const userData = sharedContent.users.getById(id);
|
const userData = shared.users.getById(id);
|
||||||
|
|
||||||
if (userData == null) return;
|
if (userData == null) return;
|
||||||
|
|
||||||
|
|
11
server/packets/TourneyJoinMatchChannel.ts
Normal file
11
server/packets/TourneyJoinMatchChannel.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { osu } from "../../osuTyping";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export function TourneyMatchJoinChannel(user:User, matchId:number) {
|
||||||
|
const match = user.shared.multiplayerManager.GetMatchById(matchId);
|
||||||
|
if (match === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match.matchChatChannel.Join(user);
|
||||||
|
}
|
10
server/packets/TourneyMatchLeaveChannel.ts
Normal file
10
server/packets/TourneyMatchLeaveChannel.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
|
||||||
|
export function TourneyMatchLeaveChannel(user:User, matchId:number) {
|
||||||
|
const match = user.shared.multiplayerManager.GetMatchById(matchId);
|
||||||
|
if (match === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match.matchChatChannel.Leave(user);
|
||||||
|
}
|
31
server/packets/TourneyMatchSpecialInfo.ts
Normal file
31
server/packets/TourneyMatchSpecialInfo.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { osu } from "../../osuTyping";
|
||||||
|
import { Match } from "../objects/Match";
|
||||||
|
import { User } from "../objects/User";
|
||||||
|
import { StatusUpdate } from "./StatusUpdate";
|
||||||
|
import { UserPresence } from "./UserPresence";
|
||||||
|
|
||||||
|
export function TourneyMatchSpecialInfo(user:User, matchId:number) {
|
||||||
|
const match = user.shared.multiplayerManager.GetMatchById(matchId);
|
||||||
|
if (!(match instanceof Match)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
|
osuPacketWriter.MatchUpdate(match.generateMatchJSON());
|
||||||
|
|
||||||
|
for (const slot of match.slots) {
|
||||||
|
if (slot.player === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const presenceBuffer = UserPresence(user, slot.player.id);
|
||||||
|
const statusBuffer = StatusUpdate(user, slot.player.id);
|
||||||
|
|
||||||
|
if (presenceBuffer instanceof Buffer && statusBuffer instanceof Buffer) {
|
||||||
|
user.addActionToQueue(presenceBuffer);
|
||||||
|
user.addActionToQueue(statusBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.addActionToQueue(osuPacketWriter.toBuffer);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,17 @@
|
||||||
import { SharedContent } from "../interfaces/SharedContent";
|
import { osu } from "../../osuTyping";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
import { User } from "../objects/User";
|
import { User } from "../objects/User";
|
||||||
const osu = require("osu-packet");
|
|
||||||
|
|
||||||
export function UserPresence(arg0:User | SharedContent, id:number) {
|
export function UserPresence(arg0:User | Shared, id:number) {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
let sharedContent:SharedContent;
|
let shared:Shared;
|
||||||
if (arg0 instanceof User) {
|
if (arg0 instanceof User) {
|
||||||
sharedContent = arg0.sharedContent;
|
shared = arg0.shared;
|
||||||
} else {
|
} else {
|
||||||
sharedContent = arg0;
|
shared = arg0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userData = sharedContent.users.getById(id);
|
const userData = shared.users.getById(id);
|
||||||
|
|
||||||
if (userData == null) return;
|
if (userData == null) return;
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
import { SharedContent } from "../interfaces/SharedContent";
|
import { osu } from "../../osuTyping";
|
||||||
|
import { Shared } from "../objects/Shared";
|
||||||
import { User } from "../objects/User";
|
import { User } from "../objects/User";
|
||||||
const osu = require("osu-packet");
|
|
||||||
|
|
||||||
export function UserPresenceBundle(arg0:User | SharedContent) : Buffer {
|
export function UserPresenceBundle(arg0:User | Shared) : Buffer {
|
||||||
const osuPacketWriter = new osu.Bancho.Writer;
|
const osuPacketWriter = osu.Bancho.Writer();
|
||||||
let sharedContent:SharedContent;
|
let shared:Shared;
|
||||||
if (arg0 instanceof User) {
|
if (arg0 instanceof User) {
|
||||||
sharedContent = arg0.sharedContent;
|
shared = arg0.shared;
|
||||||
} else {
|
} else {
|
||||||
sharedContent = arg0;
|
shared = arg0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let userIds:Array<number> = new Array<number>();
|
let userIds:Array<number> = new Array<number>();
|
||||||
|
|
||||||
for (let userData of sharedContent.users.getIterableItems()) {
|
for (let userData of shared.users.getIterableItems()) {
|
||||||
userIds.push(userData.id);
|
userIds.push(userData.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"rootDir": "./",
|
"rootDir": "./",
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"strict": true
|
"strict": true,
|
||||||
|
"lib": [
|
||||||
|
"ES2021.String"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue