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);