Skip to content
Snippets Groups Projects
Commit 956992b4 authored by Eric Oulashin's avatar Eric Oulashin Committed by Rob Swindell
Browse files

Good Time Trivia: Hosted inter-BBS user scores (via JSON DB) is now possible.

parent 609e7e3a
No related branches found
No related tags found
No related merge requests found
...@@ -26,3 +26,10 @@ answerAfterIncorrect=G ...@@ -26,3 +26,10 @@ answerAfterIncorrect=G
[CATEGORY_ARS] [CATEGORY_ARS]
dirty_minds=AGE 18 dirty_minds=AGE 18
[REMOTE_SERVER]
server=digitaldistortionbbs.com
port=10088
[SERVER]
deleteScoresOlderThanDays=182
...@@ -5,8 +5,15 @@ are saved to a file. ...@@ -5,8 +5,15 @@ are saved to a file.
Date Author Description Date Author Description
2022-11-18 Eric Oulashin Version 1.00 2022-11-18 Eric Oulashin Version 1.00
2022-11-25 Eric Oulashin Version 1.01
Added the ability to store & retrieve scores to/from a server,
so that scores from multiple BBSes can be displayed. There are
also sysop functions to remove players and users from the hosted
inter-BBS scores. Also, answer clues now don't mask spaces in the
answer.
*/ */
"use strict"; "use strict";
...@@ -30,16 +37,11 @@ if (system.version_num < 31500) ...@@ -30,16 +37,11 @@ if (system.version_num < 31500)
} }
// Version information // Version information
var GAME_VERSION = "1.00"; var GAME_VERSION = "1.01";
var GAME_VER_DATE = "2022-11-18"; var GAME_VER_DATE = "2022-11-25";
// Version of data written to the server, if applicable. This might not necessarily be the same as
// Load required .js libraries // the version of the game.
var requireFnExists = (typeof(require) === "function"); var SERVER_DATA_VERSION = "1.01";
if (requireFnExists)
require("sbbsdefs.js", "P_NONE");
else
load("sbbsdefs.js");
// Determine the location of this script (its startup directory). // Determine the location of this script (its startup directory).
// The code for figuring this out is a trick that was created by Deuce, // The code for figuring this out is a trick that was created by Deuce,
...@@ -51,6 +53,22 @@ try { throw dig.dist(dist); } catch(e) { ...@@ -51,6 +53,22 @@ try { throw dig.dist(dist); } catch(e) {
gThisScriptFilename = file_getname(e.fileName); gThisScriptFilename = file_getname(e.fileName);
} }
// Load required .js libraries
var requireFnExists = (typeof(require) === "function");
if (requireFnExists)
{
require("sbbsdefs.js", "P_NONE");
require("json-client.js", "JSONClient");
require(gStartupPath + "lib.js", "getJSONSvcPortFromServicesIni");
}
else
{
load("sbbsdefs.js");
load("json-client.js");
load(gStartupPath + "lib.js");
}
// Characters for display // Characters for display
// Box-drawing/border characters: Single-line // Box-drawing/border characters: Single-line
var UPPER_LEFT_SINGLE = "\xDA"; var UPPER_LEFT_SINGLE = "\xDA";
...@@ -109,13 +127,26 @@ var ACTION_PLAY = 0; ...@@ -109,13 +127,26 @@ var ACTION_PLAY = 0;
var ACTION_SHOW_HELP_SCREEN = 1; var ACTION_SHOW_HELP_SCREEN = 1;
var ACTION_SHOW_HIGH_SCORES = 2; var ACTION_SHOW_HIGH_SCORES = 2;
var ACTION_QUIT = 3; var ACTION_QUIT = 3;
var ACTION_SYSOP_CLEAR_SCORES = 4; var ACTION_SYSOP_MENU = 4;
// Values for JSON DB reading and writing
var JSON_DB_LOCK_READ = 1;
var JSON_DB_LOCK_WRITE = 2;
var JSON_DB_LOCK_UNLOCK = -1;
// Upon exit for any reason, make sure the scores semaphore filename doesn't exist so future instances don't get frozen // Upon exit for any reason, make sure the scores semaphore filename doesn't exist so future instances don't get frozen
//js.on_exit("if (file_exists(\"" + SCORES_SEMAPHORE_FILENAME + "\")) file_remove(\"" + SCORES_SEMAPHORE_FILENAME + "\");"); //js.on_exit("if (file_exists(\"" + SCORES_SEMAPHORE_FILENAME + "\")) file_remove(\"" + SCORES_SEMAPHORE_FILENAME + "\");");
// Enable debugging if the first command-line parameter is -debug
var gDebug = false;
if (argv.length > 0)
gDebug = (argv[0].toUpperCase() == "-DEBUG");
// Display the program logo // Display the program logo
displayProgramLogo(true, false); displayProgramLogo(true, false);
...@@ -139,26 +170,11 @@ while (continueOn) ...@@ -139,26 +170,11 @@ while (continueOn)
showHelpScreen(); showHelpScreen();
break; break;
case ACTION_SHOW_HIGH_SCORES: case ACTION_SHOW_HIGH_SCORES:
showScores(false); showScores();
break; break;
case ACTION_SYSOP_CLEAR_SCORES: case ACTION_SYSOP_MENU:
if (user.is_sysop) if (user.is_sysop)
{ doSysopMenu();
console.print("\x01n");
console.crlf();
if (file_exists(SCORES_FILENAME))
{
if (!console.noyes("\x01y\x01hAre you SURE you want to clear the scores\x01b"))
{
file_remove(SCORES_FILENAME);
console.print("\x01n\x01c\x01hThe score file has been deleted.");
}
}
else
console.print("\x01n\x01c\x01hThere is no score file yet.");
console.print("\x01n");
console.crlf();
}
break; break;
case ACTION_QUIT: case ACTION_QUIT:
default: default:
...@@ -367,6 +383,7 @@ function loadSettings(pStartupPath) ...@@ -367,6 +383,7 @@ function loadSettings(pStartupPath)
settings.behavior = iniFile.iniGetObject("BEHAVIOR"); settings.behavior = iniFile.iniGetObject("BEHAVIOR");
settings.colors = iniFile.iniGetObject("COLORS"); settings.colors = iniFile.iniGetObject("COLORS");
settings.category_ars = iniFile.iniGetObject("CATEGORY_ARS"); settings.category_ars = iniFile.iniGetObject("CATEGORY_ARS");
settings.remoteServer = iniFile.iniGetObject("REMOTE_SERVER");
// Ensure the actual expected setting name & color names exist in the settings // Ensure the actual expected setting name & color names exist in the settings
if (typeof(settings.behavior) !== "object") if (typeof(settings.behavior) !== "object")
...@@ -375,6 +392,8 @@ function loadSettings(pStartupPath) ...@@ -375,6 +392,8 @@ function loadSettings(pStartupPath)
settings.colors = {}; settings.colors = {};
if (typeof(settings.category_ars) !== "object") if (typeof(settings.category_ars) !== "object")
settings.category_ars = {}; settings.category_ars = {};
if (typeof(settings.remoteServer) !== "object")
settings.remoteServer = {};
if (typeof(settings.behavior.numQuestionsPerPlay) !== "number") if (typeof(settings.behavior.numQuestionsPerPlay) !== "number")
settings.behavior.numQuestionsPerPlay = 10; settings.behavior.numQuestionsPerPlay = 10;
...@@ -429,6 +448,8 @@ function loadSettings(pStartupPath) ...@@ -429,6 +448,8 @@ function loadSettings(pStartupPath)
settings.behavior.numTriesPerQuestion = 3; settings.behavior.numTriesPerQuestion = 3;
if (settings.behavior.maxNumPlayerScoresToDisplay <= 0) if (settings.behavior.maxNumPlayerScoresToDisplay <= 0)
settings.behavior.maxNumPlayerScoresToDisplay = 10; settings.behavior.maxNumPlayerScoresToDisplay = 10;
if (!/^[0-9]+$/.test(settings.remoteServer.port))
settings.remoteServer.port = 0;
// No need to do this: // No need to do this:
// For each color, replace any instances of specifying the control character in substWord with the actual control character // For each color, replace any instances of specifying the control character in substWord with the actual control character
...@@ -437,6 +458,25 @@ function loadSettings(pStartupPath) ...@@ -437,6 +458,25 @@ function loadSettings(pStartupPath)
iniFile.close(); iniFile.close();
} }
// Other settings - Not read from the configuration file, but things we want to use in multiple places
// in this script
// JSON scope and JSON location for scores on the server (if a server is to be used)
settings.remoteServer.gtTriviaScope = "GTTRIVIA";
// JSON location: For the BBS name, use the QWK ID if available, but if not, use the system name and replace spaces
// with underscores (since spaces may cause issues in JSON property names)
var BBS_ID = "";
if (system.qwk_id.length > 0)
BBS_ID = system.qwk_id;
else
BBS_ID = system.name.replace(/ /g, "_");
settings.remoteServer.scoresJSONLocation = "SCORES";
settings.remoteServer.BBSJSONLocation = settings.remoteServer.scoresJSONLocation + ".systems." + BBS_ID;
settings.remoteServer.userScoresJSONLocationWithoutUsername = settings.remoteServer.BBSJSONLocation + ".user_scores";
settings.hasValidServerSettings = function() {
return (this.remoteServer.hasOwnProperty("server") && this.remoteServer.hasOwnProperty("port") && this.remoteServer.server.length > 0);
};
return settings; return settings;
} }
// For configuration files, this function returns a fully-pathed filename. // For configuration files, this function returns a fully-pathed filename.
...@@ -487,6 +527,7 @@ function displayProgramLogo(pClearScreenFirst, pPauseAfter) ...@@ -487,6 +527,7 @@ function displayProgramLogo(pClearScreenFirst, pPauseAfter)
// ACTION_PLAY // ACTION_PLAY
// ACTION_SHOW_HIGH_SCORES // ACTION_SHOW_HIGH_SCORES
// ACTION_SHOW_HELP_SCREEN // ACTION_SHOW_HELP_SCREEN
// ACTION_SYSOP_MENU
// ACTION_QUIT // ACTION_QUIT
function doMainMenu() function doMainMenu()
{ {
...@@ -500,7 +541,7 @@ function doMainMenu() ...@@ -500,7 +541,7 @@ function doMainMenu()
console.print("\x01c2\x01y\x01h) \x01bHelp \x01n"); console.print("\x01c2\x01y\x01h) \x01bHelp \x01n");
console.print("\x01c3\x01y\x01h) \x01bShow high scores \x01n"); console.print("\x01c3\x01y\x01h) \x01bShow high scores \x01n");
if (user.is_sysop) if (user.is_sysop)
console.print("\x01c9\x01y\x01h) \x01bClear high scores \x01n"); // Option 9 console.print("\x01c9\x01y\x01h) \x01bSysop menu \x01n"); // Option 9
console.print("\x01cQ\x01y\x01h)\x01buit"); console.print("\x01cQ\x01y\x01h)\x01buit");
console.crlf(); console.crlf();
console.print("\x01n"); console.print("\x01n");
...@@ -515,7 +556,7 @@ function doMainMenu() ...@@ -515,7 +556,7 @@ function doMainMenu()
if (user.is_sysop) if (user.is_sysop)
validKeys += "9"; // Clear scores validKeys += "9"; // Clear scores
var userChoice = console.getkeys(validKeys, -1, K_UPPER).toString(); var userChoice = console.getkeys(validKeys, -1, K_UPPER).toString();
console.print("\x01n"); console.attributes = "N";
if (userChoice.length == 0 || userChoice == "Q") if (userChoice.length == 0 || userChoice == "Q")
menuAction = ACTION_QUIT; menuAction = ACTION_QUIT;
else if (userChoice == "1") else if (userChoice == "1")
...@@ -525,7 +566,7 @@ function doMainMenu() ...@@ -525,7 +566,7 @@ function doMainMenu()
else if (userChoice == "3") else if (userChoice == "3")
menuAction = ACTION_SHOW_HIGH_SCORES; menuAction = ACTION_SHOW_HIGH_SCORES;
else if (userChoice == "9" && user.is_sysop) else if (userChoice == "9" && user.is_sysop)
menuAction = ACTION_SYSOP_CLEAR_SCORES; menuAction = ACTION_SYSOP_MENU;
return menuAction; return menuAction;
} }
...@@ -809,26 +850,30 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -809,26 +850,30 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
} }
catch (error) catch (error)
{ {
log(LOG_ERR, GAME_NAME + " - Loading scores: " + error); log(LOG_ERR, GAME_NAME + " - Loading scores: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Loading scores: " + error); bbs.log_str(GAME_NAME + " - Loading scores: Line " + error.lineNumber + ": " + error);
} }
} }
} }
if (typeof(scoresObj) !== "object") if (typeof(scoresObj) !== "object")
scoresObj = {}; scoresObj = {};
var scoresForUser = {}; // Will store just the current user's score information
// Add/update the user's score, and save the scores file // Add/update the user's score, and save the scores file
try try
{ {
// Score object example (note: last_time is a UNIX time): // Score object example (note: last_time is a UNIX time):
// Username: // Username:
// category_stats: // category_stats:
// category_1: // 0:
// category_name: General
// last_time: 166000 // last_time: 166000
// total_score: 20 // last_score: 20
// category_2: // 1:
// category_name: Misc
// last_time: 146000 // last_time: 146000
// total_score: 80 // last_score: 80
// total_score: 100 // total_score: 100
// last_score: 20 // last_score: 20
// last_trivia_category: category_1 // last_trivia_category: category_1
...@@ -838,17 +883,54 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -838,17 +883,54 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
// Ensure the score object has an object for the current user // Ensure the score object has an object for the current user
if (!scoresObj.hasOwnProperty(user.alias)) if (!scoresObj.hasOwnProperty(user.alias))
scoresObj[user.alias] = {}; scoresObj[user.alias] = {};
// Ensure the user object in the scores object has a category_total_scores object // Ensure the user object in the scores object has a category_stats array
if (!scoresObj[user.alias].hasOwnProperty(userCategoryStatsPropName)) if (!scoresObj[user.alias].hasOwnProperty(userCategoryStatsPropName))
scoresObj[user.alias][userCategoryStatsPropName] = {}; scoresObj[user.alias][userCategoryStatsPropName] = [];
// Add/update the user's score for the category in their category_total_scores object
if (!scoresObj[user.alias][userCategoryStatsPropName].hasOwnProperty(pLastSectionName))
scoresObj[user.alias][userCategoryStatsPropName][pLastSectionName] = {};
scoresObj[user.alias][userCategoryStatsPropName][pLastSectionName].last_time = currentTime;
if (!scoresObj[user.alias][userCategoryStatsPropName][pLastSectionName].hasOwnProperty("total_score"))
scoresObj[user.alias][userCategoryStatsPropName][pLastSectionName].total_score = pUserCurrentGameScore;
else else
scoresObj[user.alias][userCategoryStatsPropName][pLastSectionName].total_score += pUserCurrentGameScore; {
// In case version 1.00 of this door has been run before, the category stats would be an object
// with category names as the properties.. Convert that to an array
if (!Array.isArray(scoresObj[user.alias][userCategoryStatsPropName]))
{
var userCatStats = [];
for (var categoryName in scoresObj[user.alias][userCategoryStatsPropName])
{
var statsObj = {
category_name: categoryName,
last_score: 0,
last_time: scoresObj[user.alias][userCategoryStatsPropName].last_time
};
// Version 1.00 has a total_score in the category sections
if (scoresObj[user.alias][userCategoryStatsPropName].hasOwnProperty("total_score"))
statsObj.last_score = scoresObj[user.alias][userCategoryStatsPropName].total_score;
else if (scoresObj[user.alias][userCategoryStatsPropName].hasOwnProperty("last_score"))
statsObj.last_score = scoresObj[user.alias][userCategoryStatsPropName].last_score;
userCatStats.push(statsObj);
}
scoresObj[user.alias][userCategoryStatsPropName] = userCatStats;
}
}
// See if the category stats already has an element with the last section name
var catStatsElementIdx = -1;
for (var i = 0; i < scoresObj[user.alias][userCategoryStatsPropName].length && catStatsElementIdx == -1; ++i)
{
if (scoresObj[user.alias][userCategoryStatsPropName][i].category_name == pLastSectionName)
catStatsElementIdx = i;
}
if (catStatsElementIdx == -1)
{
// The last section info doesn't exist in the array
scoresObj[user.alias][userCategoryStatsPropName].push({
category_name: pLastSectionName,
last_score: pUserCurrentGameScore,
last_time: currentTime
});
}
else // The last section info already exists in the array
{
scoresObj[user.alias][userCategoryStatsPropName][catStatsElementIdx].last_score = pUserCurrentGameScore;
scoresObj[user.alias][userCategoryStatsPropName][catStatsElementIdx].last_time = currentTime;
}
// Update the user's grand total score value // Update the user's grand total score value
if (scoresObj[user.alias].hasOwnProperty("total_score")) if (scoresObj[user.alias].hasOwnProperty("total_score"))
scoresObj[user.alias].total_score += pUserCurrentGameScore; scoresObj[user.alias].total_score += pUserCurrentGameScore;
...@@ -857,13 +939,14 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -857,13 +939,14 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
scoresObj[user.alias].last_score = pUserCurrentGameScore; scoresObj[user.alias].last_score = pUserCurrentGameScore;
scoresObj[user.alias].last_trivia_category = lastSectionName; scoresObj[user.alias].last_trivia_category = lastSectionName;
scoresObj[user.alias].last_time = currentTime; scoresObj[user.alias].last_time = currentTime;
scoresForUser = scoresObj[user.alias];
} }
catch (error) catch (error)
{ {
console.print("* " + error); console.print("* Line " + error.lineNumber + ": " + error);
console.crlf(); console.crlf();
log(LOG_ERR, GAME_NAME + " - Updating trivia score object: " + error); log(LOG_ERR, GAME_NAME + " - Updating trivia score object: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Updating trivia score object: " + error); bbs.log_str(GAME_NAME + " - Updating trivia score object: Line " + error.lineNumber + ": " + error);
} }
scoresFile = new File(SCORES_FILENAME); scoresFile = new File(SCORES_FILENAME);
if (scoresFile.open("w")) if (scoresFile.open("w"))
...@@ -874,13 +957,68 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -874,13 +957,68 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
// Delete the semaphore file // Delete the semaphore file
file_remove(SCORES_SEMAPHORE_FILENAME); file_remove(SCORES_SEMAPHORE_FILENAME);
// If there is a server configured, then send the user's score to the server too
if (gSettings.hasValidServerSettings())
updateScoresOnServer(user.alias, scoresForUser);
} }
// Shows the saved scores // Updates user scores on the server (if there is one configured)
// //
// Parameters: // Parameters:
// pPauseAtEnd: Boolean - Whether or not to pause at the end. The default is false. // pUserNameForScores: The user's name as used for the scores
function showScores(pPauseAtEnd) // pUserScoreInfo: An object containing user scores, as created by updateScoresFile()
function updateScoresOnServer(pUserNameForScores, pUserScoreInfo)
{
// Make sure the settings have valid server settings and the user score info object is valid
if (!gSettings.hasValidServerSettings())
return;
if (typeof(pUserNameForScores) !== "string" || pUserNameForScores.length == 0 || typeof(pUserScoreInfo) !== "object")
return;
try
{
var jsonClient = new JSONClient(gSettings.remoteServer.server, gSettings.remoteServer.port);
// Ensure the BBS name on the server has been set
var JSONLocation = gSettings.remoteServer.BBSJSONLocation + ".bbs_name";
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, system.name, JSON_DB_LOCK_WRITE);
// Write the scores on the server
JSONLocation = gSettings.remoteServer.userScoresJSONLocationWithoutUsername + "." + pUserNameForScores;
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, pUserScoreInfo, JSON_DB_LOCK_WRITE);
// Write the client & version information in the user scores too
var gameInfo = format("%s version %s (%s)", GAME_NAME, GAME_VERSION, GAME_VER_DATE);
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation + ".game_client", gameInfo, JSON_DB_LOCK_WRITE);
jsonClient.disconnect();
}
catch (error)
{
console.print("* Line " + error.lineNumber + ": " + error);
console.crlf();
log(LOG_ERR, GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error);
}
}
// Shows the saved scores - First the locally saved scores, and then if there is a
// server configured, shows the remote saved scores (after prompting the user whether
// to show those)
function showScores()
{
// Show local scores. Then, if there is a server configured, prompt the user if they
// want to see server scores, and if so, show those.
showLocalScores();
if (gSettings.hasValidServerSettings())
{
var showServerScoresConfirm = console.yesno("\x01n\x01b\x01hShow multi-BBS scores");
console.attributes = "N";
if (showServerScoresConfirm)
showServerScores();
}
}
// Shows the locally saved scores (on the current BBS)
function showLocalScores()
{ {
console.print("\x01n"); console.print("\x01n");
console.crlf(); console.crlf();
...@@ -899,28 +1037,131 @@ function showScores(pPauseAtEnd) ...@@ -899,28 +1037,131 @@ function showScores(pPauseAtEnd)
var scoresObj = JSON.parse(scoreFileContents); var scoresObj = JSON.parse(scoreFileContents);
for (var prop in scoresObj) for (var prop in scoresObj)
{ {
sortedScores.push({ sortedScores.push(new UserScoreObj(prop, scoresObj[prop].total_score, scoresObj[prop].last_score,
name: prop, scoresObj[prop].last_trivia_category, scoresObj[prop].last_time));
total_score: scoresObj[prop].total_score,
last_score: scoresObj[prop].last_score,
last_trivia_category: scoresObj[prop].last_trivia_category,
last_time: scoresObj[prop].last_time
});
} }
} }
// Sort the array: High total score first // Sort the array: High total score first
sortedScores.sort(function(objA, objB) { sortedScores.sort(userScoresSortTotalScoreHighest);
if (objA.total_score > objB.total_score)
return -1;
else if (objA.total_score < objB.total_score)
return 1;
else
return 0;
});
} }
// Print the scores if there are any // Print the scores if there are any
if (sortedScores.length > 0) if (sortedScores.length > 0)
showUserScoresArray(sortedScores);
else
console.print("\x01gThere are no saved scores yet.\x01n");
console.crlf();
}
// Shows the scores from the server (multi-BBS scores)
function showServerScores()
{
if (!gSettings.hasValidServerSettings())
return;
// Use a JSONClient to get the scores JSON from the server
try
{
var jsonClient = new JSONClient(gSettings.remoteServer.server, gSettings.remoteServer.port);
var data = jsonClient.read(gSettings.remoteServer.gtTriviaScope, "SCORES", JSON_DB_LOCK_READ);
jsonClient.disconnect();
// Example of scores from the server (as of data version 1.01):
/*
SCORES:
systems:
DIGDIST:
bbs_name: Digital Distortion
user_scores:
Nightfox:
category_stats:
0:
category_name: General
last_score: 20
last_time: 2022-11-24
total_score: 60
last_score: 20
last_trivia_category: General
last_time: 2022-11-24
game_client: Good Time Trivia version 1.01 Beta (2022-11-24)
*/
/*
if (typeof(data) === "string")
data = JSON.parse(data);
*/
// Sanity checking: Make sure the data is an object and has a "systems" property
if (typeof(data) !== "object")
{
console.attributes = "N" + gSettings.colors.error;
console.print("Invalid scores data was received from the server\x01n");
console.crlf();
return;
}
if (!data.hasOwnProperty("systems"))
{
console.attributes = "N" + gSettings.colors.error;
console.print("Invalid scores data was received from the server\x01n");
console.crlf();
return;
}
// For each BBS in the scores, sort the player scores and then show the scores for
// the BBS
for (var BBS_ID in data.systems)
{
if (!data.systems[BBS_ID].hasOwnProperty("user_scores"))
continue;
var sortedScores = [];
for (var playerName in data.systems[BBS_ID].user_scores)
{
// Player category stats are also available (as an array):
//data.systems[BBS_ID].user_scores[playerName].category_stats
var scoreObj = new UserScoreObj(playerName, data.systems[BBS_ID].user_scores[playerName].total_score, data.systems[BBS_ID].user_scores[playerName].last_score,
data.systems[BBS_ID].user_scores[playerName].last_trivia_category, data.systems[BBS_ID].user_scores[playerName].last_time);
sortedScores.push(scoreObj);
}
// Sort the array: High total score first. Then display them.
sortedScores.sort(userScoresSortTotalScoreHighest);
if (sortedScores.length > 0)
{
showUserScoresArray(sortedScores, data.systems[BBS_ID].bbs_name);
// If debugging is enabled, then also show the game_client property (game_client stores the name
// & version of the game that wrote the user score data for this player)
if (gDebug)
{ {
if (data.systems[BBS_ID].user_scores[playerName].hasOwnProperty("game_client"))
{
console.print("\x01n\x01cGame client: \x01h" + data.systems[BBS_ID].user_scores[playerName].game_client + "\x01n");
console.crlf();
}
}
console.crlf();
}
}
}
catch (error)
{
log(LOG_ERR, GAME_NAME + " - Getting server scores: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Getting server scores: Line " + error.lineNumber + ": " + error);
console.attributes = "N" + gSettings.colors.error;
console.print("* Line: " + error.lineNumber + ": " + error);
console.crlf();
}
}
// Shows an array of user scores
//
// Parameters:
// pUserScoresArray: An array of objects with the following properties:
// name: Player's name
// total_score: Player's total score from all games they've played
// last_score: The player's score from the last game they played
// last_trivia_category: The name of the last trivia category the player played
// last_time: The UNIX timestamp when the player's scores were saved
// pBBSName: Optional - The name of a BBS where the scores are coming from (if applicable)
function showUserScoresArray(pUserScoresArray, pBBSName)
{
if (typeof(pUserScoresArray) !== "object" || pUserScoresArray.length == 0)
return;
// Make the format string for printf() // Make the format string for printf()
var scoreWidth = 6; var scoreWidth = 6;
var dateWidth = 10; var dateWidth = 10;
...@@ -937,9 +1178,18 @@ function showScores(pPauseAtEnd) ...@@ -937,9 +1178,18 @@ function showScores(pPauseAtEnd)
nameWidth = console.screen_columns - (scoreWidth * 2) - 3; nameWidth = console.screen_columns - (scoreWidth * 2) - 3;
formatStr = "%-" + nameWidth + "s %" + scoreWidth + "d %" + scoreWidth + "d"; formatStr = "%-" + nameWidth + "s %" + scoreWidth + "d %" + scoreWidth + "d";
} }
// Print the scores // Show the "High scores" header
/*
if (typeof(pBBSName) === "string" && pBBSName.length > 0)
console.center("\x01g\x01hHigh Scores: \x01c" + pBBSName + "\x01n");
else
console.center("\x01g\x01hHigh Scores\x01n"); console.center("\x01g\x01hHigh Scores\x01n");
*/
console.center("\x01n\x01g\x01hHigh Scores\x01n");
if (typeof(pBBSName) === "string" && pBBSName.length > 0)
console.center("\x01n\x01gBBS\x01h: \x01c" + pBBSName);
console.crlf(); console.crlf();
// Print the scores
if (console.screen_columns >= 80) if (console.screen_columns >= 80)
{ {
printf("\x01w\x01h%-" + nameWidth + "s %-" + dateWidth + "s %-" + categoryWidth + "s %" + scoreWidth + "s %" + scoreWidth + "s", printf("\x01w\x01h%-" + nameWidth + "s %-" + dateWidth + "s %-" + categoryWidth + "s %" + scoreWidth + "s %" + scoreWidth + "s",
...@@ -956,20 +1206,20 @@ function showScores(pPauseAtEnd) ...@@ -956,20 +1206,20 @@ function showScores(pPauseAtEnd)
console.print("\x01g"); console.print("\x01g");
if (console.screen_columns >= 80) if (console.screen_columns >= 80)
{ {
for (var i = 0; i < sortedScores.length && i < gSettings.behavior.maxNumPlayerScoresToDisplay; ++i) for (var i = 0; i < pUserScoresArray.length && i < gSettings.behavior.maxNumPlayerScoresToDisplay; ++i)
{ {
var playerName = sortedScores[i].name.substr(0, nameWidth); var playerName = pUserScoresArray[i].name.substr(0, nameWidth);
var lastDate = strftime("%Y-%m-%d", sortedScores[i].last_time); var lastDate = strftime("%Y-%m-%d", pUserScoresArray[i].last_time);
var sectionName = sortedScores[i].last_trivia_category.substr(0, categoryWidth); var sectionName = pUserScoresArray[i].last_trivia_category.substr(0, categoryWidth);
printf(formatStr, playerName, lastDate, sectionName, sortedScores[i].total_score, sortedScores[i].last_score); printf(formatStr, playerName, lastDate, sectionName, pUserScoresArray[i].total_score, pUserScoresArray[i].last_score);
console.crlf(); console.crlf();
} }
} }
else else
{ {
for (var i = 0; i < sortedScores.length && i < gSettings.behavior.maxNumPlayerScoresToDisplay; ++i) for (var i = 0; i < pUserScoresArray.length && i < gSettings.behavior.maxNumPlayerScoresToDisplay; ++i)
{ {
printf(formatStr, sortedScores[i].name.substr(0, nameWidth), sortedScores[i].total_score, sortedScores[i].last_score); printf(formatStr, pUserScoresArray[i].name.substr(0, nameWidth), pUserScoresArray[i].total_score, pUserScoresArray[i].last_score);
console.crlf(); console.crlf();
} }
} }
...@@ -978,11 +1228,27 @@ function showScores(pPauseAtEnd) ...@@ -978,11 +1228,27 @@ function showScores(pPauseAtEnd)
console.print(HORIZONTAL_DOUBLE); console.print(HORIZONTAL_DOUBLE);
console.crlf(); console.crlf();
} }
// Creates a user score object for display in the high scores
function UserScoreObj(pPlayerName, pTotalScore, pLastScore, pLastCategory, pLastTime)
{
this.name = pPlayerName;
this.total_score = pTotalScore;
this.last_score = pLastScore;
this.last_trivia_category = pLastCategory;
this.last_time = pLastTime;
}
// An array sorting function for UserScoreObj objects to sort the array by
// highest total_score first
function userScoresSortTotalScoreHighest(pPlayerA, pPlayerB)
{
if (pPlayerA.total_score > pPlayerB.total_score)
return -1;
else if (pPlayerA.total_score < pPlayerB.total_score)
return 1;
else else
console.print("\x01gThere are no saved scores yet.\x01n"); return 0;
console.crlf();
if (typeof(pPauseAtEnd) === "boolean" && pPauseAtEnd)
console.pause();
} }
// Displays the game help to the user // Displays the game help to the user
...@@ -1060,7 +1326,8 @@ function showHelpScreen() ...@@ -1060,7 +1326,8 @@ function showHelpScreen()
console.pause(); console.pause();
} }
// Returns a version of a string that is masked, possibly with some of its characters unmasked // Returns a version of a string that is masked, possibly with some of its characters unmasked.
// Spaces will not be included.
// //
// Parameters: // Parameters:
// pStr: A string to mask // pStr: A string to mask
...@@ -1068,23 +1335,38 @@ function showHelpScreen() ...@@ -1068,23 +1335,38 @@ function showHelpScreen()
// pMaskChar: Optional - The mask character. Defaults to "*". // pMaskChar: Optional - The mask character. Defaults to "*".
function partiallyHiddenStr(pStr, pNumLettersUncovered, pMaskChar) function partiallyHiddenStr(pStr, pNumLettersUncovered, pMaskChar)
{ {
if (typeof(pStr) !== "string") if (typeof(pStr) !== "string" || pStr.length == 0)
return ""; return "";
// Count the number of spaces in the string
var numSpaces = 0;
for (var i = 0; i < pStr.length; ++i)
{
if (pStr.charAt(i) == " ")
++numSpaces;
}
var maskChar = (typeof(pMaskChar) === "string" && pMaskChar.length > 0 ? pMaskChar.substr(0, 1) : "*"); var maskChar = (typeof(pMaskChar) === "string" && pMaskChar.length > 0 ? pMaskChar.substr(0, 1) : "*");
var numLetersUncovered = (typeof(pNumLettersUncovered) === "number" && pNumLettersUncovered > 0 ? pNumLettersUncovered : 0); var numLetersUncovered = (typeof(pNumLettersUncovered) === "number" && pNumLettersUncovered > 0 ? pNumLettersUncovered : 0);
var str = ""; var str = "";
if (numLetersUncovered >= pStr.length) if (numLetersUncovered >= pStr.length - numSpaces) //if (numLetersUncovered >= pStr.length)
str = pStr; str = pStr;
else else
{ {
var i = 0; var i = 0;
var charCount = 0;
for (i = 0; i < pStr.length; ++i) for (i = 0; i < pStr.length; ++i)
{ {
if (i < numLetersUncovered) var currentChar = pStr.charAt(i);
str += pStr.charAt(i); if (currentChar == " ")
{
str += " ";
continue;
}
if (charCount++ < numLetersUncovered)
str += currentChar;
else else
str += maskChar; str += maskChar;
} }
} }
return str; return str;
...@@ -1132,3 +1414,148 @@ function attrCodeStr(pAttrCodeCharStr) ...@@ -1132,3 +1414,148 @@ function attrCodeStr(pAttrCodeCharStr)
str += "\x01" + pAttrCodeCharStr[i]; str += "\x01" + pAttrCodeCharStr[i];
return str; return str;
} }
// Sysop menu: Provides the sysop with some maintenance options
function doSysopMenu()
{
if (!user.is_sysop)
return;
var continueOn = true;
while (continueOn)
{
console.attributes = "N";
console.print("\x01c\x01hSysop menu");
console.crlf();
console.attributes = "NB";
for (var i = 0; i < console.screen_columns-1; ++i)
console.print(HORIZONTAL_DOUBLE);
console.crlf();
var validKeys = "1Q"; // Clear high scores, Quit
console.print("\x01c1\x01y\x01h) \x01bClear high scores\x01n");
console.print(" \x01cQ\x01y\x01h)\x01buit\x01n");
// If there is an inter-BBS scores JSON file, then add some options to manage that
if (file_exists(backslash(gStartupPath + "server") + "gttrivia.json"))
{
validKeys += "23";
console.crlf();
console.print("\x01gInter-BBS scores\x01n");
console.crlf();
console.attributes = "KH";
for (var i = 0; i < 16; ++i)
console.print(HORIZONTAL_SINGLE);
console.attributes = "N";
console.crlf();
console.print("\x01c2\x01y\x01h) \x01bDelete user (from all systems)\x01n");
console.crlf();
console.print("\x01c3\x01y\x01h) \x01bDelete BBS scores\x01n");
console.crlf();
}
console.attributes = "NB";
for (var i = 0; i < console.screen_columns-1; ++i)
console.print(HORIZONTAL_DOUBLE);
console.attributes = "N";
console.crlf();
console.print("\x01cYour choice\x01g\x01h: \x01c");
var userChoice = console.getkeys(validKeys, -1, K_UPPER).toString();
console.attributes = "N";
if (userChoice.length == 0 || userChoice == "Q")
continueOn = false;
else if (userChoice == "1")
{
console.crlf();
if (file_exists(SCORES_FILENAME))
{
if (!console.noyes("\x01y\x01hAre you SURE you want to clear the scores\x01b"))
{
file_remove(SCORES_FILENAME);
console.print("\x01n\x01c\x01hThe score file has been deleted.");
}
}
else
console.print("\x01n\x01c\x01hThere is no score file yet.");
console.print("\x01n");
console.crlf();
}
else if (userChoice == "2")
{
// Delete user from all systems from server scores
console.print("\x01cPlayer name\x01g\x01h: \x01c");
var playerName = console.getstr("", -1, K_UPRLWR);
if (playerName.length > 0)
{
if (!console.noyes("\x01y\x01hAre you SURE you want to remove \x01g" + playerName + "\x01b"))
{
var localJSONServicePort = getJSONSvcPortFromServicesIni();
var jsonClient = new JSONClient("127.0.0.1", localJSONServicePort);
try
{
var data = jsonClient.read(gSettings.remoteServer.gtTriviaScope, "SCORES", JSON_DB_LOCK_READ);
if (typeof(data) === "object" && data.hasOwnProperty("systems"))
{
for (var BBS_ID in data.systems)
{
if (data.systems[BBS_ID].hasOwnProperty("user_scores"))
{
var playerNameUpper = playerName.toUpperCase();
for (var playerName in data.systems[BBS_ID].user_scores)
{
if (playerName.toUpperCase() == playerNameUpper)
{
var JSONLocation = format("SCORES.systems.%s.user_scores.%s", BBS_ID, playerName);
jsonClient.remove(gSettings.remoteServer.gtTriviaScope, JSONLocation, JSON_DB_LOCK_WRITE);
}
}
}
}
}
}
catch (error)
{
console.print("* " + error + "\r\n");
log(LOG_ERR, GAME_NAME + " - Deleting user from server scores: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Deleting user from server scores: Line " + error.lineNumber + ": " + error);
}
jsonClient.disconnect();
}
}
}
else if (userChoice == "3")
{
// Delete BBS from server scores
console.print("\x01cBBS name\x01g\x01h: \x01c");
var BBSName = console.getstr("", -1, K_UPRLWR);
if (BBSName.length > 0)
{
if (!console.noyes("\x01y\x01hAre you SURE you want to remove \x01g" + BBSName + "\x01b"))
{
var localJSONServicePort = getJSONSvcPortFromServicesIni();
var jsonClient = new JSONClient("127.0.0.1", localJSONServicePort);
try
{
var data = jsonClient.read(gSettings.remoteServer.gtTriviaScope, "SCORES", JSON_DB_LOCK_READ);
if (typeof(data) === "object" && data.hasOwnProperty("systems"))
{
var BBSNameUpper = BBSName.toUpperCase();
for (var BBS_ID in data.systems)
{
if (data.systems[BBS_ID].hasOwnProperty("bbs_name") && data.systems[BBS_ID].bbs_name.toUpperCase() == BBSNameUpper)
{
var JSONLocation = format("SCORES.systems.%s", BBS_ID);
jsonClient.remove(gSettings.remoteServer.gtTriviaScope, JSONLocation, JSON_DB_LOCK_WRITE);
}
}
}
}
catch (error)
{
console.print("* " + error + "\r\n");
log(LOG_ERR, GAME_NAME + " - Deleting user from server scores: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Deleting user from server scores: Line " + error.lineNumber + ": " + error);
}
jsonClient.disconnect();
}
}
}
}
}
\ No newline at end of file
// Gets the JSON service port number from services.ini (if possible)
function getJSONSvcPortFromServicesIni()
{
var portNum = 0;
var servicesIniFile = new File(system.ctrl_dir + "services.ini");
if (servicesIniFile.open("r"))
{
var sectionNamesToTry = [ "JSON_DB", "JSON-DB", "JSON" ];
for (var i = 0; i < sectionNamesToTry.length; ++i)
{
var jsonServerCfg = servicesIniFile.iniGetObject(sectionNamesToTry[i]);
if (jsonServerCfg != null && typeof(jsonServerCfg) === "object")
{
portNum = jsonServerCfg.Port;
break;
}
}
servicesIniFile.close();
}
return portNum;
}
\ No newline at end of file
...@@ -1426,7 +1426,7 @@ I'm a 4-letter word ending in UNT. You do me when you lose your balls. When you' ...@@ -1426,7 +1426,7 @@ I'm a 4-letter word ending in UNT. You do me when you lose your balls. When you'
Hunt Hunt
10 10
I hang between your chest and knes. The older I am, the bigger I get. There's hair growing on my hole. I hang between your chest and knees. The older I am, the bigger I get. There's hair growing on my hole.
Belly Belly
10 10
......
...@@ -1347,7 +1347,7 @@ Zloty ...@@ -1347,7 +1347,7 @@ Zloty
10 10
How tall is the Empire State Building (without the spire and antenna)? How tall is the Empire State Building (without the spire and antenna)?
1,250 feet 1250 feet
10 10
Which country produces the most tea? Which country produces the most tea?
......
Good Time Trivia Good Time Trivia
Version 1.00 Version 1.01
Release date: 2022-11-18 Release date: 2022-11-25
by by
...@@ -22,6 +22,9 @@ Contents ...@@ -22,6 +22,9 @@ Contents
3. Installation & Setup 3. Installation & Setup
- Installation in SCFG - Installation in SCFG
4. Configuration file 4. Configuration file
5. Optional: Configuring your BBS to host player scores
- Only do this if you would prefer to host scores on your BBS rather than
using the inter-BBS scores hosted on Digital Distortion
1. Disclaimer 1. Disclaimer
...@@ -84,7 +87,11 @@ of the following files and directories: ...@@ -84,7 +87,11 @@ of the following files and directories:
3. gttrivia.asc The logo/startup screen to be shown to the user. 3. gttrivia.asc The logo/startup screen to be shown to the user.
This is in Synchronet attribute code format. This is in Synchronet attribute code format.
4. qa This is a subdirectory that contains the trivia 4. install-xtrn.ini A configuration file for automated installation into
Synchronet's external programs section; for use with
install-xtrn.js. This is optional.
5. qa This is a subdirectory that contains the trivia
question & answer files. Each file contains a question & answer files. Each file contains a
collection of questions, answers, and number of collection of questions, answers, and number of
points for each question. Each filename must have points for each question. Each filename must have
...@@ -93,6 +100,17 @@ of the following files and directories: ...@@ -93,6 +100,17 @@ of the following files and directories:
questions (underscores are required between each questions (underscores are required between each
word). The filename extension must be .qa . word). The filename extension must be .qa .
6. server This directory contains a couple scripts that are
used if you enable the gttrivia JSON database (for
hosting game scores on a Synchronet BBS so that scores
from players on multiple BBSes can be hosted and
displayed). As of this writing, I host scores for
Good Time Trivia on my BBS (Digital Distortion), so
unless you really want to do your own score hosting,
you can have your installation of the game read
inter-BBS scores from Digital Distortion.
The trivia category files (in the qa directory, with filenames ending in .qa) The trivia category files (in the qa directory, with filenames ending in .qa)
are plain text files and contain questions, their answers, and their number of are plain text files and contain questions, their answers, and their number of
points. For eqch question in a category file, there are 3 lines: points. For eqch question in a category file, there are 3 lines:
...@@ -135,7 +153,8 @@ gttrivia.js. ...@@ -135,7 +153,8 @@ gttrivia.js.
Installation in SCFG Installation in SCFG
-------------------- --------------------
This is an example of adding the game in one of your external programs sections This is an example of adding the game in one of your external programs sections
in SCFG: in SCFG (note that the 'Native Executable/Script value doesn't matter for JS
scripts):
╔═════════════════════════════════════════════════════[< >]╗ ╔═════════════════════════════════════════════════════[< >]╗
║ Good Time Trivia ║ ║ Good Time Trivia ║
╠══════════════════════════════════════════════════════════╣ ╠══════════════════════════════════════════════════════════╣
...@@ -149,7 +168,7 @@ in SCFG: ...@@ -149,7 +168,7 @@ in SCFG:
║ │Execution Requirements ║ ║ │Execution Requirements ║
║ │Multiple Concurrent Users Yes ║ ║ │Multiple Concurrent Users Yes ║
║ │I/O Method FOSSIL or UART ║ ║ │I/O Method FOSSIL or UART ║
║ │Native Executable/Script No ║ │Native Executable/Script Yes
║ │Use Shell or New Context No ║ ║ │Use Shell or New Context No ║
║ │Modify User Data No ║ ║ │Modify User Data No ║
║ │Execute on Event No ║ ║ │Execute on Event No ║
...@@ -166,6 +185,7 @@ gttrivia.ini is the configuration file for the door game. There are 3 sections: ...@@ -166,6 +185,7 @@ gttrivia.ini is the configuration file for the door game. There are 3 sections:
[BEHAVIOR], [COLORS], and [CATEGORY_ARS]. The settings are described below: [BEHAVIOR], [COLORS], and [CATEGORY_ARS]. The settings are described below:
[BEHAVIOR] section [BEHAVIOR] section
------------------
Setting Description Setting Description
------- ----------- ------- -----------
numQuestionsPerPlay The maximum number of trivia questions numQuestionsPerPlay The maximum number of trivia questions
...@@ -181,10 +201,12 @@ maxNumPlayerScoresToDisplay The maximum number of player scores to display ...@@ -181,10 +201,12 @@ maxNumPlayerScoresToDisplay The maximum number of player scores to display
in the list of high scores in the list of high scores
[COLORS] section [COLORS] section
----------------
In this section, the color codes are simply specified by a string of color In this section, the color codes are simply specified by a string of color
(attribute) code characters (i.e., YH for yellow and high). See this page for (attribute) code characters (i.e., YH for yellow and high). See this page for
Synchronet attribute codes: Synchronet attribute codes:
http://wiki.synchro.net/custom:ctrl-a_codes http://wiki.synchro.net/custom:ctrl-a_codes
Setting Element applied to Setting Element applied to
------- ------------------- ------- -------------------
error Errors error Errors
...@@ -211,6 +233,7 @@ clue Clue text ...@@ -211,6 +233,7 @@ clue Clue text
answerAfterIncorrect The answer printed after incorrect response answerAfterIncorrect The answer printed after incorrect response
[CATEGORY_ARS] section [CATEGORY_ARS] section
----------------------
In this section, the format is section_name=ARS string In this section, the format is section_name=ARS string
section_name must match the part of a filename in the qa directory without the section_name must match the part of a filename in the qa directory without the
filename extension. The ARS string is a string that Synchronet uses to describe filename extension. The ARS string is a string that Synchronet uses to describe
...@@ -220,3 +243,57 @@ categories that you may want age-restricted, for instance). See the following ...@@ -220,3 +243,57 @@ categories that you may want age-restricted, for instance). See the following
web page for documentation on Synchronet's ARS strings: web page for documentation on Synchronet's ARS strings:
http://wiki.synchro.net/access:requirements http://wiki.synchro.net/access:requirements
[REMOTE_SERVER] section
-----------------------
This section is used for specifying a remote server to connect to. Currently,
this is used for writing & reading player scores on the remote system. Inter-
BBS scores (retrieved from the remote system) can be optionally viewed when a
user is viewing scores.
Setting Description
------- -----------
server The server hostname/IP address. The default
value can be used if you want to use Digital
Distortion BBS.
port The port number to use to connect to the
remote host. The default value is set for
Digital Distortion.
[SERVER] section
----------------
This section is only used if you decide to host scores for Good Time Trivia.
Setting Description
------- -----------
deleteScoresOlderThanDays The number of days to keep old player scores.
The background service will remove player
scores older than this number of days.
5. Optional: Configuring your BBS to host player scores
=======================================================
You should only do this if you would prefer to host scores on your BBS rather
than using the inter-BBS scores hosted on Digital Distortion.
If you want to host player scores for Good Time Trivia, first ensure you have a
section in your ctrl/services.ini configured to run json-service.js (usually
with a name of JSON, or JSON in the name):
[JSON]
Port=10088
Options=STATIC | LOOP
Command=json-service.js
Note that those settings configure it to run on port 10088. Also, you might
already have a similar section in your services.ini if you currently host any
JSON databases.
Then, open your ctrl/json-service.ini and add these lines to enable the gttrivia
JSON dtaabase:
[gttrivia]
dir=../xtrn/gttrivia/server/
It would then probably be a good idea to stop and re-start your Synchronet BBS
in order for it to recognize that you have a new JSON database configured.
...@@ -4,4 +4,11 @@ Revision History (change log) ...@@ -4,4 +4,11 @@ Revision History (change log)
============================= =============================
Version Date Description Version Date Description
------- ---- ----------- ------- ---- -----------
1.01 2022-11-25 Added the ability to store & retrieve scores to/from a
server, so that scores from multiple BBSes can be
displayed. By default, it's configured to use Digital
Distortion as the host. There are also sysop functions
to remove players and users from the hosted inter-BBS
scores. Also, answer clues now don't mask spaces in the
answer.
1.00 2022-11-18 Initial version/release 1.00 2022-11-18 Initial version/release
\ No newline at end of file
this.QUERY = function(client, packet)
{
// Operations that are allowed by clients
var openOpers = [ "READ", "WRITE", "SUBSCRIBE", "UNSUBSCRIBE" ];
// The openOpers operations are okay to run; also, allow anything from the current machine
if (openOpers.indexOf(packet.oper) >= 0 || client.remote_ip_address === '127.0.0.1')
return false;
else
return true;
// Disallowed operations
//var invalidOps = [ "PUSH", "POP", "SHIFT", "UNSHIFT", "DELETE", "SLICE" ];
}
// For Good Time Trivia JSON database.
// This is a long-running background script.
// Values for JSON DB reading and writing
var JSON_DB_LOCK_READ = 1;
var JSON_DB_LOCK_WRITE = 2;
var JSON_DB_LOCK_UNLOCK = -1;
var NUM_SECONDS_PER_DAY = 86400;
// gRootTriviaScriptDir is the root directory where service.js is located
var gRootTriviaScriptDir = argv[0];
var requireFnExists = (typeof(require) === "function");
if (requireFnExists)
{
require("json-client.js", "JSONClient");
require(gRootTriviaScriptDir + "../lib.js", "getJSONSvcPortFromServicesIni");
}
else
{
load("json-client.js");
load(gRootTriviaScriptDir + "../lib.js");
}
var gSettings, gJSONClient;
function processUpdate(update)
{
log(LOG_INFO, "Good Time Trivia: Update");
if (gSettings.server.deleteScoresOlderThanDays > 0)
{
// Look through the server data for old scores that we might want to delete
try
{
var data = gJSONClient.read(gSettings.remoteServer.gtTriviaScope, "SCORES", JSON_DB_LOCK_READ);
// Example of scores from the server (as of data version 1.01):
/*
SCORES:
systems:
DIGDIST:
bbs_name: Digital Distortion
user_scores:
Nightfox:
category_stats:
0:
category_name: General
last_score: 20
last_time: 2022-11-24
total_score: 60
last_score: 20
last_trivia_category: General
last_time: 2022-11-24
*/
/*
if (typeof(data) === "string")
data = JSON.parse(data);
*/
// Sanity checking: Make sure the data is an object and has a "systems" property
if (typeof(data) !== "object")
return;
if (!data.hasOwnProperty("systems"))
return;
for (var BBS_ID in data.systems)
{
var now = time();
if (!data.systems[BBS_ID].hasOwnProperty("user_scores"))
continue;
for (var playerName in data.systems[BBS_ID].user_scores)
{
if (data.systems[BBS_ID].user_scores[playerName].last_time < now - (NUM_SECONDS_PER_DAY * gSettings.server.deleteScoresOlderThanDays))
{
// Delete this user's entry
var JSONLocation = format("SCORES.systems.%s.user_scores.%s", BBS_ID, playerName);
gJSONClient.remove(gtTriviaScope, JSONLocation, LOCK_WRITE);
}
}
}
}
catch (err)
{
log(LOG_ERR, "Good Time Trivia: Line " + err.lineNumber + ": " + err);
}
}
}
function init()
{
// Assuming this script is in a 'server' subdirectory, gttrivia.ini should be one directory up
gSettings = readSettings(gRootTriviaScriptDir + "../");
try
{
gJSONClient = new JSONClient("127.0.0.1", getJSONSvcPortFromServicesIni());
gJSONClient.subscribe(gSettings.remoteServer.gtTriviaScope, "SCORES");
gJSONClient.callback = processUpdate;
processUpdate();
log(LOG_INFO, "Good Time Trivia JSON DB service task initialized");
}
catch (error)
{
log(LOG_ERROR, error);
}
}
var readSettings = function(path)
{
var settings = {
readSuccessful: false
};
var iniFile = new File(path + "gttrivia.ini");
if (iniFile.open("r"))
{
settings.remoteServer = iniFile.iniGetObject("REMOTE_SERVER");
settings.server = iniFile.iniGetObject("SERVER");
iniFile.close();
settings.readSuccessful = true;
if (typeof(settings.remoteServer) !== "object")
settings.remoteServer = {};
if (!/^[0-9]+$/.test(settings.remoteServer.port))
settings.remoteServer.port = 0;
if (typeof(settings.server.deleteScoresOlderThanDays) !== "number")
settings.server.deleteScoresOlderThanDays = 0;
else if (settings.server.deleteScoresOlderThanDays < 0)
settings.server.deleteScoresOlderThanDays = 0;
// Other settings - Not read from the configuration file, but things we want to use in multiple places
// in this script
// JSON scope
settings.remoteServer.gtTriviaScope = "GTTRIVIA";
if (settings.server.deleteScoresOlderThanDays > 0)
log(LOG_INFO, "Good Time Trivia service: Will delete user scores older than " + settings.server.deleteScoresOlderThanDays + " days");
}
return settings;
}
function main()
{
while (!js.terminated)
{
mswait(5);
gJSONClient.cycle();
}
}
function cleanUp()
{
gJSONClient.disconnect();
}
try
{
init();
main();
cleanUp();
} catch (err) { }
exit();
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment