[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