[Scummvm-git-logs] scummvm-sites multiplayer -> 0c4aeb997753d6b086570798affba0e96af1255a

LittleToonCat noreply at scummvm.org
Wed Oct 25 05:08:29 UTC 2023


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm-sites' repo located at https://github.com/scummvm/scummvm-sites .

Summary:
0c4aeb9977 MULTIPLAYER: Baseball stats logging + version restrictions.


Commit: 0c4aeb997753d6b086570798affba0e96af1255a
    https://github.com/scummvm/scummvm-sites/commit/0c4aeb997753d6b086570798affba0e96af1255a
Author: Little Cat (toontownlittlecat at gmail.com)
Date: 2023-10-25T02:08:01-03:00

Commit Message:
MULTIPLAYER: Baseball stats logging + version restrictions.

Changed paths:
    lobby/config.yaml
    lobby/database/Redis.js
    lobby/database/WebAPI.js
    lobby/discord/Discord.js
    lobby/global/Stats.js
    lobby/net/AreaMessages.js
    lobby/net/DatabaseMessages.js
    lobby/net/NetworkListener.js
    lobby/package-lock.json
    lobby/package.json


diff --git a/lobby/config.yaml b/lobby/config.yaml
index c53de91..f6c7368 100644
--- a/lobby/config.yaml
+++ b/lobby/config.yaml
@@ -12,4 +12,11 @@ network:
 
   # This sets the session server that the game will connect to
   # to host and join their sessions.
-  session_server: "127.0.0.1:9120"
\ No newline at end of file
+  session_server: "127.0.0.1:9120"
+
+  # This restricts access from certain versions of ScummVM.
+  # Setting the build version by a date will disable access who have built
+  # ScummVM prior to that date.  Setting it to null will restrict that version
+  # all together.
+  version_restrictions:
+    2.8.0git: "Oct 25 2023 UTC"
diff --git a/lobby/database/Redis.js b/lobby/database/Redis.js
index c21d6bc..f9d259f 100644
--- a/lobby/database/Redis.js
+++ b/lobby/database/Redis.js
@@ -24,11 +24,13 @@
 const createLogger = require('logging').default;
 const ioredis = require("ioredis")
 const Areas = require('../global/Areas.js');
+const { default: Redlock } = require('redlock');
 
 class Redis {
     constructor(config) {
         this.logger = createLogger("Redis");
         this.redis = new ioredis(config);
+        this.redlock = new Redlock([this.redis]);
 
         this.redis.on("ready", async () => {
             this.logger.info("Connected");
@@ -78,6 +80,7 @@ class Redis {
             'icon': Number(response['icon']),
             'stats': stats
             .split(',').map(Number),
+            'version': response['version'],
             'game': response['game'],
             'area': Number(response['area']),
             'inGame': Number(response['inGame']),
@@ -94,8 +97,9 @@ class Redis {
         return undefined;
     }
 
-    async addUser(userId, user, game) {
+    async addUser(userId, user, version, game) {
         // Add some server specific keys.
+        user.version = version
         user.game = game;
         user.area = 0;
         user.phone = 0;
@@ -106,14 +110,14 @@ class Redis {
         await this.redis.hset("byonline:users:nameToId", user['user'].toUpperCase(), userId);
     }
 
-    async getUser(username, password, game) {
+    async getUser(username, password, version, game) {
         if (database != this) {
             this.logger.warn("Redis isn't set as default database, calling getUser shouldn't be possible");
             return {error: 1, message: "Internal error."};
         }
         let user = await this.getUserByName(username);
         if (user) {
-            await this.addUser(user.id, user, game)
+            await this.addUser(user.id, user, version, game)
             return user;
         } else {
             const userId = await this.redis.incr("byonline:globals:userId")
@@ -123,7 +127,7 @@ class Redis {
                 'f_stats': Array(42).fill(0),
                 'b_stats': Array(29).fill(0),
             }
-            this.addUser(userId, user, game);
+            this.addUser(userId, user, version, game);
             user['id'] = userId;
             return user;
         }
@@ -163,6 +167,14 @@ class Redis {
         await this.redis.hset(`byonline:users:${userId}`, 'icon', icon);
     }
 
+    async setStats(userId, stats, game) {
+        let field = "stats"
+        if (database == this) {
+            field = game == "football" ? "f_stats" : "b_stats";
+        }
+        await this.redis.hset(`byonline:users:${userId}`, field, String(stats));
+    }
+
     async getUserIdsInArea(areaId, game) {
         return await this.redis.lrange(`byonline:areas:${game}:${areaId}`, 0, -1)
         .then((users) => {
diff --git a/lobby/database/WebAPI.js b/lobby/database/WebAPI.js
index 340b56f..02dc30d 100644
--- a/lobby/database/WebAPI.js
+++ b/lobby/database/WebAPI.js
@@ -34,7 +34,7 @@ class WebAPI {
         this.post = bent(endpoint, 'POST', 'json', 200);
     }
 
-    async getUser(username, password, game) {
+    async getUser(username, password, version, game) {
         const user = await this.post('/new_login', {token: this.token,
                                                     user: username,
                                                     pass: password,
@@ -45,7 +45,7 @@ class WebAPI {
         // Store the user into the Redis cache
         redis.addUser(user.id, {user: user.user,
                                 icon: user.icon,
-                                stats: user.stats}, game);
+                                stats: user.stats}, version, game);
         return user;
     }
 
@@ -67,6 +67,19 @@ class WebAPI {
         redis.setIcon(userId, icon);
     }
 
+    async setStats(userId, stats, game) {
+        const response = await this.post('/set_stats', {token: this.token,
+                                                        userId: userId,
+                                                        stats: stats,
+                                                        game: game});
+        if (response.error) {
+            this.logger.error("Failed the update stats!", { response });
+        }
+
+        // Set the stats in the Redis cache.
+        redis.setStats(userId, stats, game);
+    }
+
     async getTeam(userId, game) {
         const response = await this.post('/get_team', {token: this.token,
                                                        userId: userId,
diff --git a/lobby/discord/Discord.js b/lobby/discord/Discord.js
index d6366ba..5924c66 100644
--- a/lobby/discord/Discord.js
+++ b/lobby/discord/Discord.js
@@ -130,9 +130,9 @@ class Discord {
         area = `${user.inGame ? '(In-Game) ' : ''}(${Areas[user.area]}, ${groupName})`;
       }
       if (user.game == "baseball") {
-        baseballUsers += `${user.user} ${area}\n`;
+        baseballUsers += `${user.user} (${user.version}) ${area}\n`;
       } else {
-        footballUsers += `${user.user} ${area}\n`;
+        footballUsers += `${user.user} (${user.version}) ${area}\n`;
       }
     }
 
diff --git a/lobby/global/Stats.js b/lobby/global/Stats.js
index 5444eca..d5c874d 100644
--- a/lobby/global/Stats.js
+++ b/lobby/global/Stats.js
@@ -20,11 +20,53 @@
  */
 
 "use strict";
+const createLogger = require('logging').default;
+const logger = createLogger('Stats');
+
+const ProfileMappers = {
+    "baseball": (profileFields) => {
+        const profile = {
+            wins: profileFields[0],
+            losses: profileFields[1],
+            disconnects: profileFields[2],
+            winStreak: profileFields[3],
+            lastTenGames: profileFields[4],
+            margin: profileFields[5],
+            careerTotalGames: profileFields[6],
+            careerAtBats: profileFields[7],
+            careerHits: profileFields[8],
+            // NOTE: Batting average is fortunately calculated in-game,
+            // setting this field in the database will not change anything.
+            careerBattingAverage: profileFields[9],
+            careerSingles: profileFields[10],
+            careerDoubles: profileFields[11],
+            careerTriples: profileFields[12],
+            careerHomeRuns: profileFields[13],
+            careerRuns: profileFields[14],
+            careerSteals: profileFields[15],
+            careerStrikeouts: profileFields[16],
+            careerWalks: profileFields[17],
+            gameHits: profileFields[18],
+            gameSingles: profileFields[19],
+            gameDoubles: profileFields[20],
+            gameTriples: profileFields[21],
+            gameHomeRuns: profileFields[22],
+            gameRuns: profileFields[23],
+            gameSteals: profileFields[24],
+            gameStrikeouts: profileFields[25],
+            gameWalks: profileFields[26],
+            longestHomeRun: profileFields[27],
+            poll: profileFields[28],
+            // chatEnabled: profileFields[29]
+        }
+        return profile;
+    }
+}
 
 const ResultsMappers = {
     // These take an array that the game sends to `/game_results`
     // and return an ongoing results object that can be stored in redis
-    "baseball": (resultsFields, isHome, opponentId) => {
+    "baseball": (resultsFields, isHome, opponentId, last) => {
         const ongoingResults = {
             winning: resultsFields[0],
             runs: resultsFields[1],
@@ -42,15 +84,168 @@ const ResultsMappers = {
             completedInnings: resultsFields[13],
             isHome: isHome,
             opponentId: opponentId,
+            last: last,
         };
         return ongoingResults;
     },
-    // TODO: Football
-    "football": (resultsFields, isHome, opponentId) => {
-        return {"not_yet_supported": 1}
+    "football": (resultsFields, isHome, opponentId, last) => {
+        const ongoingResults = {
+            winning: resultsFields[0],
+            score: resultsFields[1],
+            passingYards: resultsFields[2],
+            passingAttempts: resultsFields[3],
+            completePasses: resultsFields[4],
+            rushingYards: resultsFields[5],
+            rushingAttempts: resultsFields[6],
+            // NOTE: field 7 is rushing completes, but is commented
+            // off the original network code, so this is omitted.
+            fumbles: resultsFields[8],
+            fumblesLost: resultsFields[9],
+            fieldGoalAttempts: resultsFields[10],
+            fieldGoalsMade: resultsFields[11],
+            puntAttempts: resultsFields[12],
+            puntYards: resultsFields[13],
+            puntBlocks: resultsFields[14],
+            thirdDowns: resultsFields[15],
+            thirdDownsConverted: resultsFields[16],
+            fourthDowns: resultsFields[17],
+            fourthDownsConverted: resultsFields[18],
+            defenseInterceptions: resultsFields[19],
+            defenseFumbles: resultsFields[20],
+            defenseSacks: resultsFields[21],
+            longestPass: resultsFields[22],
+            puntReturn: resultsFields[23],
+            opponentDefenseInterceptions: resultsFields[24],
+            opponentPassingYards: resultsFields[25],
+            opponentRushingYards: resultsFields[26],
+            opponentScore: resultsFields[27],
+            opponentThirdDowns: resultsFields[28],
+            opponentThirdDownsConverted: resultsFields[29],
+            opponentFourthDowns: resultsFields[30],
+            opponentFourthDownsConverted: resultsFields[31],
+            disconnect: resultsFields[32],
+            quarter: resultsFields[33],
+            isHome: isHome,
+            opponentId: opponentId,
+            last: last
+        };
+        return ongoingResults;
     }
 };
 
+const CalculateStats = {
+    "baseball": async (userId, gameResults, opponentId, opponentGameResults) => {
+        const user = await redis.getUserById(userId, "baseball");
+        if (Object.keys(user).length == 0) {
+            // TODO: User must've disconnected from the lobby, get
+            // stats from the WebAPI and work from there.
+            logger.warn(`TODO: User ${userId} not in redis.`);
+            return
+        }
+        const stats = ProfileMappers["baseball"](user.stats);
+
+        const opponent = await redis.getUserById(opponentId, "baseball");
+        if (Object.keys(opponent).length == 0) {
+            // TODO: User must've disconnected from the lobby, get
+            // stats from the WebAPI and work from there.
+            logger.warn(`TODO: User ${opponentId} not in redis.`);
+            return
+        }
+        const opponentStats = ProfileMappers["baseball"](opponent.stats);
+
+        function getLastTenWinLosses(value) {
+            return [value & 1023, value >> 10];
+        }
+
+        function convertLastTenWinLosses([wins, losses]) {
+            let value = losses << 10;
+            value += wins;
+            return value;
+        }
+
+        function calculateWinLoss(stats, gameResults) {
+            let [lastTenWins, lastTenLosses] = getLastTenWinLosses(stats.lastTenGames);
+            if (gameResults["winning"]) {
+                // Record win.
+                stats.wins += 1;
+                stats.winStreak += 1;
+                if (lastTenWins < 10) {
+                    lastTenWins++;
+                }
+                if (lastTenLosses > 0) {
+                    lastTenLosses--;
+                }
+            } else {
+                // Record loss.
+                stats.losses += 1;
+                if (gameResults["disconnect"]) {
+                    stats.disconnects += 1;
+                }
+                stats.winStreak = 0;
+                if (lastTenWins > 0) {
+                    lastTenWins--;
+                }
+                if (lastTenLosses < 10) {
+                    lastTenLosses++;
+                }
+            }
+
+            stats.lastTenGames = convertLastTenWinLosses([lastTenWins, lastTenLosses]);
+
+            // TODO: Calculate stats.margin.
+        }
+
+        function calculateCareerRecords(stats, gameResults) {
+            // Career records
+            stats.careerTotalGames++;
+            stats.careerAtBats += gameResults["atBats"];
+            stats.careerHits += gameResults["hits"];
+            // The game calculates the batting average, so skipping.
+            stats.careerSingles += gameResults["singles"]
+            stats.careerDoubles += gameResults["doubles"]
+            stats.careerTriples += gameResults["triples"]
+            stats.careerHomeRuns += gameResults["homeRuns"]
+            stats.careerRuns += gameResults["runs"]
+            stats.careerSteals += gameResults["steals"]
+            stats.careerStrikeouts += gameResults["strikeouts"]
+            stats.careerWalks += gameResults["walks"]
+
+            // We don't know what the "game" stats are used for, so we're ignoring them for now.
+
+            if (gameResults["longestHomeRun"] > stats.longestHomeRun) {
+                stats.longestHomeRun = gameResults["longestHomeRun"]
+            }
+        }
+
+        // Check for disconnects.  This value gets set when someone loses connection to their other player
+        // (1), or forfeits a match (2), and it could happen either they were winning or not.
+        // We do this check so the disconnector will be credited as a lost and their opponent
+        // as a winner.
+        if (gameResults["disconnect"]) {
+            opponentGameResults["winning"] = 1;
+            gameResults["winning"] = 0;
+        } else if (opponentGameResults["disconnect"]) {
+            gameResults["winning"] = 1;
+            opponentGameResults["winning"] = 0;
+        }
+
+        // Calculate win/losses
+        calculateWinLoss(stats, gameResults);
+        calculateWinLoss(opponentStats, opponentGameResults);
+
+        calculateCareerRecords(stats, gameResults);
+        calculateCareerRecords(opponentStats, opponentGameResults);
+
+        return [stats, opponentStats];
+    },
+    "football": async (userId, gameResults, opponentId, opponentGameResults) => {
+        // TODO: Football stats
+        return [{}, {}]
+    }
+}
+
 module.exports = {
+    ProfileMappers: ProfileMappers,
     ResultsMappers: ResultsMappers,
+    CalculateStats: CalculateStats,
 };
diff --git a/lobby/net/AreaMessages.js b/lobby/net/AreaMessages.js
index bcc9391..95c3762 100644
--- a/lobby/net/AreaMessages.js
+++ b/lobby/net/AreaMessages.js
@@ -36,11 +36,18 @@ server.handleMessage('get_population', async (client, args) => {
     let population = 0;
     if (areaId in Areas.Groups) {
         for (const area of Areas.Groups[areaId]) {
-            const users = await redis.getUserIdsInArea(area, client.game);
-            population += users.length;
+            const users = await redis.getUsersInArea(area, client.game);
+            for (const user of users) {
+                if (client.versionNumber == user.version)
+                    population += 1
+            }
         }
     } else {
-        population = (await redis.getUserIdsInArea(areaId, client.game)).length;
+        const users = await redis.getUsersInArea(areaId, client.game);
+        for (const user of users) {
+            if (client.versionNumber == user.version)
+                population += 1
+        }
     }
     client.send('population_resp', {area: areaId, population: population});
 
@@ -90,8 +97,8 @@ server.handleMessage('get_players', async (client, args) => {
 
     const players = [];
     for (const user of users) {
-        if (user.id == client.userId) {
-            // Don't add ourselves in.
+        if ((user.id == client.userId) || (user.version != client.versionNumber)) {
+            // Don't add ourselves or mismatched versions in.
             continue;
         }
         players.push([user.user, user.id, user.icon, user.stats[0], user.stats[1], user.stats[2], user.phone, user.opponent]);
@@ -109,8 +116,8 @@ process.on('update_players_list', (args) => {
         if (client.areaId == areaId && client.game == game) {
             const players = [];
             for (const user of users) {
-                if (user.id == client.userId) {
-                    // Don't add ourselves in.
+                if ((user.id == client.userId) || (user.version != client.versionNumber)) {
+                    // Don't add ourselves or mismatched versions in.
                     continue;
                 }
                 players.push([user.user, user.id, user.icon, user.stats[0], user.stats[1], user.stats[2], user.phone, user.opponent]);
@@ -138,9 +145,6 @@ server.handleMessage('game_finished', async (client, args) => {
     logEvent('game_finished', client, args.version, {'area': client.areaId, 'opponent': client.opponentId});
     await redis.setInGame(client.userId, 0);
     await redis.sendGamesPlayingInArea(client.areaId, client.game);
-
-    await redis.removeOngoingResults(client.opponentId, client.game);
-    await redis.removeOngoingResults(client.userId, client.game);
 });
 
 process.on('update_games_playing', async (args) => {
diff --git a/lobby/net/DatabaseMessages.js b/lobby/net/DatabaseMessages.js
index 7d7da57..8fa09a8 100644
--- a/lobby/net/DatabaseMessages.js
+++ b/lobby/net/DatabaseMessages.js
@@ -35,19 +35,77 @@ server.handleMessage("login", async (client, args) => {
     const competitive_mods = args.competitive_mods;
 
     if (username === undefined) {
-        client.kick("Missing username parameter!");
+        client.send("login_resp", {error_code: 1,
+                                   id: 0,
+                                   sessionServer: "",
+                                   response: "Missing username parameter!"});
         return;
     } else if (password === undefined) {
-        client.kick("Missing password parameter!");
+        client.send("login_resp", {error_code: 1,
+                                   id: 0,
+                                   sessionServer: "",
+                                   response: "Missing password parameter!"});
         return;
     } else if (game === undefined) {
-        client.kick("Missing game parameter!");
+        client.send("login_resp", {error_code: 1,
+                                   id: 0,
+                                   sessionServer: "",
+                                   response: "Missing game parameter!"});
         return;
     } else if (version == undefined) {
-        client.kick("Missing version paremeter!");
+        client.send("login_resp", {error_code: 1,
+                                   id: 0,
+                                   sessionServer: "",
+                                   response: "Missing version paremeter!"});
         return;
     }
 
+    // This code parses the ScummVM version string sent by the client.
+    // e.g. ScummVM 2.8.0git-{revision} (Oct 21 2023 19:11:48)
+    const versionArray = version.split(" ");
+    if (versionArray[0] != "ScummVM") {
+        client.send("login_resp", {error_code: 1,
+                                   id: 0,
+                                   sessionServer: "",
+                                   response: "Only ScummVM clients are supported."});
+        return;
+    }
+    client.versionNumber = versionArray[1]
+    if (client.versionNumber.includes("git")) {
+        // This is a development build, exclude the revision since it does not matter here.
+        const gitLocation = client.versionNumber.indexOf("git")
+        // This should result with "2.8.0git"
+        client.versionNumber = client.versionNumber.substr(0, gitLocation + 3)
+    }
+
+    if (client.versionNumber in server.versionRestrictions) {
+        if (server.versionRestrictions[client.versionNumber] == null) {
+            // Discontinued
+            logEvent('discontinuedLogin', client, version, {'username': username, 'game': game, 'server_version': server.versionRestrictions[client.versionNumber]});
+            client.send("login_resp", {error_code: 1,
+                                       id: 0,
+                                       sessionServer: "",
+                                       response: `ScummVM version ${client.versionNumber} is no longer being supported.  Please visit scummvm.org and update to the latest version to continue playing online.`})
+            return;
+        }
+        // Parse version date and check against the timestamp in the config.
+        // The substr call is to remove the first bracket from the date string.
+
+        const clientTimestamp = Date.parse(`${versionArray[2].substr(1)} ${versionArray[3]} ${versionArray[4]} UTC`);
+        const serverTimestamp = Date.parse(server.versionRestrictions[client.versionNumber])
+
+        const isBuildCompatable = clientTimestamp >= serverTimestamp
+        if (!isBuildCompatable) {
+            // Outdated build.
+            logEvent('outdatedLogin', client, version, {'username': username, 'game': game, 'server_version': server.versionRestrictions[client.versionNumber]});
+            client.send("login_resp", {error_code: 1,
+                                       id: 0,
+                                       sessionServer: "",
+                                       response: `This build of ${client.versionNumber} is no longer being supported.  Please download the latest daily build or pull and build the latest changes to continue playing online.`});
+            return;
+        }
+    }
+
     const games = ["football", "baseball"];
     if (!games.includes(game)) {
         client.kick("Game not supported.");
@@ -57,7 +115,7 @@ server.handleMessage("login", async (client, args) => {
     client.version = version;
     client.competitiveMods = competitive_mods || false;
 
-    const user = await database.getUser(username, password, game);
+    const user = await database.getUser(username, password, client.versionNumber, game);
     logEvent('login', client, args.version, {'user': user.id, 'username': user.user, 'game': game, 'competitive_mods': competitive_mods});
     if (user.error) {
         client.send("login_resp", {error_code: user.error,
@@ -144,8 +202,8 @@ server.handleMessage('locate_player', async (client, args) => {
                       area: ""};
 
     const user = await redis.getUserByName(username);
-    if (!user || !user.game || user.game != client.game) {
-        // Player not logged in or in the different game
+    if (!user || !user.game || user.game != client.game || user.version != client.versionNumber) {
+        // Player is either not logged in, playing a different game, or using a different version.
         client.send("locate_resp", response);
         return
     }
@@ -167,6 +225,7 @@ server.handleMessage('locate_player', async (client, args) => {
 server.handleMessage("game_results", async (client, args) => {
     const resultsUserId = args.user;
     const reportingUserId = client.userId;
+    const lastFlag = args.last;
     let isHome;
     let opponentId;
     // The home team always reports the game results, so we can use that
@@ -180,12 +239,44 @@ server.handleMessage("game_results", async (client, args) => {
         opponentId = client.userId;
     }
     const resultsFields = args.fields;
-    const ongoingResults = Stats.ResultsMappers[client.game](
-        resultsFields, isHome, opponentId
+    const results = Stats.ResultsMappers[client.game](
+        resultsFields, isHome, opponentId, Number(lastFlag)
     );
-    logEvent('game_results', client, args.version, {'results': ongoingResults, 'rawResults': resultsFields});
-
-    await redis.setOngoingResults(resultsUserId, client.game, ongoingResults);
+    logEvent('game_results', client, args.version, {'resultsUserId': resultsUserId, 'results': results, 'rawResults': resultsFields});
+
+    // Set a lock here to ensure that CalculateStats gets called only once.
+    await redis.redlock.using([resultsUserId, opponentId], 5000, async (signal) => {
+        await redis.setOngoingResults(resultsUserId, client.game, results);
+
+        if (!lastFlag) {
+            // We got the results, but the game is still on-going.
+            return;
+        }
+
+        // Get the opponent's own final results and calcuate stats for both.
+        const opponentResultsStrings = await redis.getOngoingResults(opponentId, client.game);
+        const opponentResults = Object.fromEntries(
+            Object.entries(opponentResultsStrings).map(([k, stat]) => [k, Number(stat)])
+        );
+        if (Object.keys(opponentResults).length == 0 || !opponentResults.last) {
+            // We have not gotten the final results for the opponent yet, return and wait.
+            return;
+        }
+        console.log(results, opponentResults)
+
+        const [changedStats, opponentChangedStats] = await Stats.CalculateStats[client.game](resultsUserId, results, opponentId, opponentResults);
+        logEvent('updated_stats', client, args.version, {'resultsUserId': resultsUserId, 'stats': changedStats, 'rawStats': Object.values(changedStats), 'opponentId': opponentId, 'opponentStats': opponentChangedStats, 'rawOpponentStats': Object.values(opponentChangedStats)});
+
+        // Store in database:
+        if (Object.keys(changedStats).length > 0)
+            await database.setStats(resultsUserId, Object.values(changedStats), client.game);
+        if (Object.keys(opponentChangedStats).length > 0)
+            await database.setStats(opponentId, Object.values(opponentChangedStats), client.game);
+
+        // Now we should be done with the results, erase them from Redis
+        await redis.removeOngoingResults(reportingUserId, client.game);
+        await redis.removeOngoingResults(opponentId, client.game);
+    });
 });
 
 server.handleMessage('get_teams', async (client, args) => {
diff --git a/lobby/net/NetworkListener.js b/lobby/net/NetworkListener.js
index 417086f..fece79c 100644
--- a/lobby/net/NetworkListener.js
+++ b/lobby/net/NetworkListener.js
@@ -48,6 +48,8 @@ class NetworkListener {
         // Store session server address
         this.sessionServer = this.config['session_server'] || '127.0.0.1:9120';
 
+        this.versionRestrictions = this.config['version_restrictions'] || {}
+
         const host = this.config['host'];
         const port = Number(this.config['port']);
         const keyPath = this.config['key'];
diff --git a/lobby/package-lock.json b/lobby/package-lock.json
index f031d60..9aaf241 100644
--- a/lobby/package-lock.json
+++ b/lobby/package-lock.json
@@ -12,7 +12,8 @@
         "bent": "^7.3.12",
         "ioredis": "^4.27.7",
         "js-yaml": "^4.1.0",
-        "logging": "^3.3.0"
+        "logging": "^3.3.0",
+        "redlock": "^5.0.0-beta.2"
       },
       "optionalDependencies": {
         "discord.js": "^13.1.0",
@@ -494,6 +495,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/node-abort-controller": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
+      "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="
+    },
     "node_modules/node-fetch": {
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -575,6 +581,17 @@
         "node": ">=4"
       }
     },
+    "node_modules/redlock": {
+      "version": "5.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/redlock/-/redlock-5.0.0-beta.2.tgz",
+      "integrity": "sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==",
+      "dependencies": {
+        "node-abort-controller": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/require-from-string": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -1102,6 +1119,11 @@
         }
       }
     },
+    "node-abort-controller": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
+      "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="
+    },
     "node-fetch": {
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -1154,6 +1176,14 @@
         "redis-errors": "^1.0.0"
       }
     },
+    "redlock": {
+      "version": "5.0.0-beta.2",
+      "resolved": "https://registry.npmjs.org/redlock/-/redlock-5.0.0-beta.2.tgz",
+      "integrity": "sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==",
+      "requires": {
+        "node-abort-controller": "^3.0.1"
+      }
+    },
     "require-from-string": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
diff --git a/lobby/package.json b/lobby/package.json
index d168f7b..3ccb046 100644
--- a/lobby/package.json
+++ b/lobby/package.json
@@ -20,7 +20,8 @@
     "bent": "^7.3.12",
     "ioredis": "^4.27.7",
     "js-yaml": "^4.1.0",
-    "logging": "^3.3.0"
+    "logging": "^3.3.0",
+    "redlock": "^5.0.0-beta.2"
   },
   "optionalDependencies": {
     "discord.js": "^13.1.0",




More information about the Scummvm-git-logs mailing list