206 lines
6.1 KiB
TypeScript
206 lines
6.1 KiB
TypeScript
import express, { Request, Response } from 'express';
|
|
import ReadSettings from './settings';
|
|
import { GetRandomPort, OpenGameInstance } from './helpers';
|
|
import { Settings, Player, Room, QueueEntry } from './types';
|
|
|
|
const version = "v1.0";
|
|
console.log("Starting Cupid Matchmaker for unity " + version);
|
|
|
|
const app = express();
|
|
const queueGraceTime = 2000;
|
|
|
|
// Read settings
|
|
const settings = ReadSettings();
|
|
if (settings == null) {
|
|
console.error("Failed to read settings");
|
|
process.exit(1);
|
|
}
|
|
|
|
// Type assertion since we've already checked for null
|
|
const typedSettings = settings as Settings;
|
|
const logLevel = typedSettings.log_level;
|
|
|
|
LogVerbose(typedSettings);
|
|
|
|
let Rooms: Room[] = [];
|
|
let Queue: QueueEntry[] = [];
|
|
|
|
app.get('/settings', (req: Request, res: Response) => {
|
|
if (req.query.password !== typedSettings.password) {
|
|
res.send("403 Unauthorized");
|
|
return;
|
|
}
|
|
const m_settings = {
|
|
minimum_players: typedSettings.minimum_players,
|
|
maximum_players: typedSettings.maximum_players,
|
|
waiting_time: typedSettings.waiting_time,
|
|
port_range_min: typedSettings.port_range_min,
|
|
port_range_max: typedSettings.port_range_max,
|
|
games: typedSettings.games
|
|
};
|
|
console.log(m_settings);
|
|
res.send(m_settings);
|
|
});
|
|
|
|
app.get('/', (req: Request, res: Response) => {
|
|
const username = req.query.username?.toString() ?? "";
|
|
const game_name = req.query.game_name?.toString() ?? "";
|
|
|
|
|
|
if (!ValidateRequest(req, res)) {
|
|
return;
|
|
}
|
|
|
|
// Check query
|
|
let joinedRoom: Room | null = null;
|
|
let alreadyOnQueue = false;
|
|
|
|
Queue.forEach(element => {
|
|
if (element.Name === username) {
|
|
element.LastSeen = Date.now();
|
|
alreadyOnQueue = true;
|
|
} else {
|
|
if (Date.now() - element.LastSeen > queueGraceTime) {
|
|
const index = Queue.indexOf(element);
|
|
if (index > -1) {
|
|
Queue.splice(index, 1);
|
|
}
|
|
Log(`Player ${element.Name} is afk, removing.`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Check on rooms if not in the queue, Maybe they already joined a room
|
|
let possibleRoom: Room | null = null;
|
|
|
|
Rooms.forEach(element => {
|
|
let roomValid = true;
|
|
if (Date.now() - element.InitTime > typedSettings.waiting_time) {
|
|
// This room is expired
|
|
LogDebug("This room is expired, Removing now");
|
|
LogDebug(element);
|
|
const index = Rooms.indexOf(element);
|
|
if (index > -1) {
|
|
Rooms.splice(index, 1);
|
|
}
|
|
roomValid = false;
|
|
}
|
|
if (roomValid) {
|
|
element.Players.forEach(player => {
|
|
if (player.Name === username) {// Already in a room
|
|
joinedRoom = element;
|
|
}
|
|
});
|
|
if (element.Players.length === 0) {
|
|
// This room is empty, remove it
|
|
|
|
const index = Rooms.indexOf(element);
|
|
if (index > -1) {
|
|
Rooms.splice(index, 1);
|
|
}
|
|
} else if (element.Players.length < typedSettings.maximum_players && element.GameName === game_name) {
|
|
possibleRoom = element;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (joinedRoom !== null) { // Already in a room. Stopping here
|
|
res.send(joinedRoom);
|
|
return;
|
|
}
|
|
|
|
// Neither in a room nor in queue, Let's see
|
|
if (possibleRoom === null) {
|
|
if (Queue.length >= typedSettings.minimum_players) {
|
|
const newPort = GetRandomPort(typedSettings.port_range_min, typedSettings.port_range_max);
|
|
const newRoom: Room = {
|
|
Players: [{ Name: username, LastSeen: Date.now() }],
|
|
GameName: game_name,
|
|
Port: newPort,
|
|
InitTime: Date.now()
|
|
};
|
|
Rooms.push(newRoom);
|
|
//TODO: Get the correct game exe from the game id
|
|
OpenGameInstance(typedSettings.games[0].exe, newPort);
|
|
res.send(newRoom);
|
|
return;
|
|
}
|
|
} else {
|
|
// We know possibleRoom is a Room type here since we checked it's not null
|
|
const room: Room = possibleRoom;
|
|
room.Players.push({ Name: username, LastSeen: Date.now() });
|
|
res.send(room);
|
|
return;
|
|
}
|
|
|
|
// Not even got a new room. Back to queue
|
|
if (!alreadyOnQueue) {
|
|
const newQueueEntry: QueueEntry = { Name: username, LastSeen: Date.now() };
|
|
LogVerbose(newQueueEntry);
|
|
Queue.push(newQueueEntry);
|
|
}
|
|
LogVerbose("Rooms");
|
|
Rooms.forEach(element => {
|
|
LogVerbose(element);
|
|
});
|
|
LogVerbose("Queue");
|
|
LogVerbose(Queue);
|
|
res.send("0");
|
|
});
|
|
|
|
app.get('/cancel', (req: Request, res: Response) => {
|
|
const username = req.query.username?.toString() ?? "";
|
|
|
|
if (!ValidateRequest(req, res)) {
|
|
return;
|
|
}
|
|
let foundUser = false;
|
|
Queue.forEach((element) => {
|
|
if (element.Name === username) {
|
|
const index = Queue.indexOf(element);
|
|
if (index > -1) {
|
|
Queue.splice(index, 1);
|
|
}
|
|
res.send("1");
|
|
foundUser = true;
|
|
return;
|
|
}
|
|
});
|
|
if (!foundUser) {
|
|
res.send("Couldn't find user " + username + " in the queue");
|
|
}
|
|
});
|
|
|
|
function ValidateRequest(req: Request, res: Response): boolean {
|
|
const username = req.query.username?.toString() ?? "";
|
|
const password = req.query.password?.toString() ?? "";
|
|
if (password !== typedSettings.password) {
|
|
res.send("403 Unauthorized");
|
|
LogVerbose("Unauthorized call " + password + ":" + typedSettings.password);
|
|
return false;
|
|
}
|
|
if (username.length < 2) {
|
|
res.send("Bad credentials");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function Log(msg: any): void {
|
|
console.log(msg);
|
|
}
|
|
|
|
function LogDebug(msg: any): void {
|
|
if (logLevel > 0) {
|
|
console.log(msg);
|
|
}
|
|
}
|
|
|
|
function LogVerbose(msg: any): void {
|
|
if (logLevel > 1) {
|
|
console.log(msg);
|
|
}
|
|
}
|
|
|
|
app.listen(typedSettings.port);
|
|
console.log("Listening on port " + typedSettings.port);
|