¶ GameLoaderCopyright(c) 2015 Stefano Balietti MIT Licensed Loads nodeGame games from file system |
"use strict";
module.exports = GameLoader;
|
¶ Global scope |
var path = require('path');
var fs = require('fs');
var GameRouter = require('./GameRouter');
var J = require('JSUS').JSUS;
var ngc = require('nodegame-client');
var GameStage = ngc.GameStage;
var serverVersionObj;
|
¶ GameLoader constructorConstructs a new instance of GameLoader |
function GameLoader(servernode) {
this.servernode = servernode;
this.gameRouter = new GameRouter(servernode);
|
Cannot keep a reference to servernode.logger because it gets overwritten later on. |
|
Transform server version string to object (used to check dependencies). |
serverVersionObj = version2GameStage(this.servernode.version);
}
|
¶ GameLoader methods |
|
¶ GameLoader.updateGameInfoUpdates information about an existing game Params
gameName
string
The name of the game
update
object
An object containing properties to mixin
|
GameLoader.prototype.updateGameInfo = function(gameName, update) {
var gameInfo;
if ('string' !== typeof gameName) {
throw new TypeError('GameLoader.updateGameInfo: gameName must ' +
'be string.');
}
if ('object' !== typeof update) {
throw new TypeError('GameLoader.updateGameInfo: update must ' +
'be object.');
}
gameInfo = this.servernode.info.games[gameName];
if (!gameInfo) {
throw new TypeError('GameLoader.updateGameInfo: game not found: ' +
gameName + '.');
}
J.mixin(gameInfo, update);
return gameInfo;
};
|
¶ GameLoader.addGameAdds a new game to the global collection The version of ServerNode is compared against the information in gameInfo.engines.nodegame, if found. Aliases will be created, if needed. Params
gameInfo
object
A game descriptor, usually from package.json
gameDir
string
The root directory of the game (with trailing slash)
See
ServerNode.addGameDir
|
GameLoader.prototype.addGame = function(gameInfo, gameDir) {
var gamePath, reqFail;
var gameLogicPath, gameLogic, gameClientPath, gameClient;
var clientTypes, langObject;
var gameType, gameGame, game, gameName;
var channel;
if ('object' !== typeof gameInfo) {
throw new TypeError('GameLoader.addGame: gameInfo must be object.');
}
if ('string' !== typeof gameDir) {
throw new TypeError('GameLoader.addGame: gameDir must be string.');
}
gameName = gameInfo.name;
|
Check name. |
if ('string' !== typeof gameName) {
throw new Error('GameLoader.addGame: missing or invalid game name: ' +
gameDir + '.');
}
|
Checking nodeGame server version requirement. |
if (gameInfo.engines) {
reqFail = gameRequirementsFail(gameInfo.engines.nodegame);
if (reqFail) {
throw new Error('GameLoader.addGame: game ' + gameName + ' ' +
'requires nodeGame version ' +
gameInfo.engines.nodegame + ' ' +
'but found ' + this.servernode.version + '.');
}
}
|
Loading settings, setup, and stages. |
gameGame = this.loadGameGameDir(gameDir);
|
Loading client types. |
clientTypes = this.loadClientTypes(gameDir);
|
Building language object. |
langObject = this.buildLanguagesObject(gameDir);
|
Adding game info to global info object. |
this.servernode.info.games[gameName] = {
dir: gameDir,
info: gameInfo,
clientTypes: clientTypes,
settings: gameGame.settings,
setup: gameGame.setup,
stager: gameGame.stager,
languages: langObject,
channel: {},
alias: []
};
|
Load server dir first (channels, etc.). |
channel = this.loadChannelDir(gameDir, gameName);
|
Creates a waiting room. |
this.loadWaitRoomDir(gameDir, channel);
|
Loading Auth dir, if any. |
this.loadAuthDir(gameDir, channel);
|
Loading Requirements dir, if any. |
this.loadRequirementsDir(gameDir, channel);
|
Tries to load Views file from game directory. this.loadViewsFile(gameDir, gameInfo); |
|
Add routes (considering other loaded options: auth, monitor, etc.). |
this.gameRouter.addRoutes(gameDir, gameName,
this.servernode.info.games[gameName]);
|
Done. |
this.servernode.logger.info('GameLoader.addGame: added ' +
gameName + ': ' + gameDir);
};
|
¶ GameLoader.loadGameGameDirLoads the game dir of the game Params
directory
string
The path of the game directory
Returns
object
An object containing three properties:
'settings', 'stager', and 'setup'.
See
GameLoader.addGame
See
parseGameSettings
|
GameLoader.prototype.loadGameGameDir = function(directory) {
var gameSetupPath, gameSetup;
var gameSettingsPath, gameSettings;
var gameStagesPath, gameStages;
var stager;
if ('string' !== typeof directory) {
throw new TypeError('GameLoader.loadGameGameDir: directory must be ' +
'string.');
}
|
Settings. |
gameSettingsPath = directory + 'game/game.settings.js';
try {
gameSettings = require(gameSettingsPath);
}
catch(e) {
throw new Error('GameLoader.loadGameGameDir: cannot read ' +
gameSettingsPath + ': ' + e.stack);
}
if ('object' !== typeof gameSettings) {
throw new Error('SeverNode.loadGameGameDir: invalid settings file: ' +
gameSettingsPath + '.');
}
|
Stages. |
gameStagesPath = directory + 'game/game.stages.js';
try {
gameStages = require(gameStagesPath);
}
catch(e) {
throw new Error('GameLoader.loadGameGameDir: cannot read ' +
gameStagesPath + ': ' + e.stack);
}
if ('function' !== typeof gameStages) {
throw new Error('SeverNode.loadGameGameDir: stages file did not ' +
'export a valid function: ' + gameStagesPath + '.');
}
stager = ngc.getStager();
gameStages = gameStages(stager, gameSettings);
|
Setup. |
gameSetupPath = directory + 'game/game.setup.js';
try {
gameSetup = require(gameSetupPath);
}
catch(e) {
throw new Error('GameLoader.loadGameGameDir: cannot read ' +
gameSetupPath + ': ' + e.stack);
}
if ('function' !== typeof gameSetup) {
throw new Error('SeverNode.loadGameGameDir: setup file did not ' +
'export a valid function: ' + gameSetupPath + '.');
}
gameSetup = gameSetup(gameSettings, gameStages);
if ('object' !== typeof gameSetup) {
throw new Error('SeverNode.loadGameGameDir: setup function did ' +
'not return a valid object: ' + gameSetupPath + '.');
}
|
Parsing the settings object before returning. |
gameSettings = parseGameSettings(gameSettings);
if ('object' !== typeof gameSettings) {
throw new Error('SeverNode.loadGameGameDir: error parsing ' +
'treatment object: ' + gameSettingsPath + '.');
}
return {
setup: gameSetup,
settings: gameSettings,
stager: stager
};
};
|
¶ GameLoader.loadClientTypesLoads client types from game/client_types directory Params
directory
string
The path of the game directory
See
GameLoader.addGame
|
GameLoader.prototype.loadClientTypes = function(directory) {
var clientTypesDir, fileNames, fileName;
var clientType, clientTypes, clientTypePath;
var i, len;
var logger;
if ('string' !== typeof directory) {
throw new TypeError('GameLoader.loadClientTypes: directory must be ' +
'string.');
}
logger = this.servernode.logger;
clientTypesDir = directory + 'game/client_types/';
if (!J.existsSync(clientTypesDir)) {
logger.error('GameLoader.loadClientTypes: game directory not ' +
'found: ' + directory);
return;
}
fileNames = fs.readdirSync(clientTypesDir);
clientTypes = {};
i = -1, len = fileNames.length;
for ( ; ++i < len ; ) {
fileName = fileNames[i];
|
Ignore non-js files, and temporary and hidden files (begin with '.'). |
if (path.extname(fileName) === '.js' && fileName.charAt(0) !== '.') {
clientTypePath = clientTypesDir + fileName;
if (!fs.statSync(clientTypePath).isDirectory()) {
try {
clientType = require(clientTypePath);
clientTypes[fileName.slice(0,-3)] = clientType;
}
catch(e) {
logger.error('GameLoader.loadClientTypes: cannot ' +
'read ' + clientTypePath + ': ' + e.stack);
throw e;
}
}
}
}
|
Checking if mandatory types are found. |
if (!clientTypes.logic) {
throw new Error('GameLoader.loadClientTypes: logic type not found.');
}
if (!clientTypes.player) {
throw new Error('GameLoader.loadClientTypes: player type not found.');
}
return clientTypes;
};
|
¶ GameLoader.buildLanguagesObjectBuilds an object containing language objects for a given game directory Params
directory
string
The directory of the game.
Returns
object
languages Object of language objects.
|
GameLoader.prototype.buildLanguagesObject = function(directory) {
var ctxPath, langPaths, languages, languageObject;
var i, len, langFile;
ctxPath = directory + '/views/contexts/';
if (!fs.existsSync(ctxPath)) return;
langPaths = fs.readdirSync(ctxPath);
languages = {};
i = -1, len = langPaths.length;
for ( ; ++i < len ; ) {
languageObject = {};
langFile = ctxPath + langPaths[i] + '/languageInfo.json';
if (fs.existsSync(langFile)) languageObject = require(langFile);
languageObject.shortName = langPaths[i];
languages[languageObject.shortName] = languageObject;
}
return languages;
};
|
¶ GameLoader.loadChannelDirLoads several files from Read: channel.settings.js, channel.credentials.js, _channel.secret.js Params
directory
string
The directory of the game.
gameName
string
The name of the game.
Returns
ServerChannel
The created channel.
See
GameLoader.loadChannelFile
See
ServerChannel
|
GameLoader.prototype.loadChannelDir = function(directory, gameName) {
var settings, pwdFile, jwtFile;
var channel;
|
|
settings = this.loadChannelFile(directory, gameName);
|
Save ref. to newly created channel. |
channel = this.servernode.channels[gameName];
|
|
pwdFile = directory + 'channel/channel.credentials.js';
loadSyncAsync(pwdFile, settings, 'loadChannelDir', function(credentials) {
|
TODO: checkings. Add method. |
channel.credentials = credentials;
});
|
|
jwtFile = directory + 'channel/channel.secret.js';
loadSyncAsync(jwtFile, settings, 'loadChannelDir', function(secret) {
|
TODO: checkings. Add method. |
channel.secret = secret;
});
return channel;
};
|
¶ GameLoader.loadChannelFileLoads channel.settings.js from file system and adds channels accordingly Synchronously looks for a file called channel.settings.js at the top level of the specified directory. The file channel.settings.js must export one channel object in the format specified by the ServerChannel constructor. Every channel configuration object can optionally have a waitingRoom object to automatically add a waiting room to the channel. Params
directory
string
The directory of the game
gameName
string
The name of the game
See
ServerChannel
See
ServerChannel.createWaitingRoom
See
ServerNode.addChannel
|
GameLoader.prototype.loadChannelFile = function(directory, gameName) {
var channelsFile;
var channel, waitRoom;
var i, len;
var channelConf, waitRoomConf;
if ('string' !== typeof directory) {
throw new TypeError('GameLoader.loadChannelFile: directory must be ' +
'string.');
}
channelsFile = directory + '/channel/channel.settings.js';
if (!fs.existsSync(channelsFile)) return;
channelConf = require(channelsFile);
|
Validate |
if ('object' !== typeof channelConf) {
throw new TypeError('GameLoader.loadChannelFile:' +
'channels must be object. Directory: ' +
directory);
}
if (channelConf.waitingRoom &&
'object' !== typeof channelConf.waitingRoom) {
throw new TypeError('GameLoader.loadChannelFile: waitingRoom ' +
'in channel configuration must be object. ' +
'Directory: ' + directory);
}
waitRoomConf = channelConf.waitingRoom;
delete channelConf.waitingRoom;
if (!channelConf.name) channelConf.name = gameName;
channelConf.gameName = gameName;
channel = this.servernode.addChannel(channelConf);
if (channel) {
|
Add the list of channels created by the game. |
this.servernode.info.games[gameName].channel = {
player: channel.playerServer ?
channel.playerServer.endpoint : null,
admin: channel.adminServer ?
channel.adminServer.endpoint : null
};
if (waitRoomConf) {
|
We prepend the baseDir parameter, if any. If logicPath is not string we let the WaitingRoom constructor throw an error. |
if ('string' === typeof waitRoomConf.logicPath) {
waitRoomConf.logicPath = checkLocalPath(
directory, 'channel/', waitRoomConf.logicPath);
}
waitRoom = channel.createWaitingRoom(waitRoomConf);
if (!waitRoom) {
throw new Error('GameLoader.loadChannelFile: could ' +
'not add waiting room to channel ' +
channel.name);
}
}
|
Adding channel alias. |
if (channelConf.alias) {
this.createGameAlias(channelConf.alias, gameName);
}
}
|
Errors in channel creation are already logged. |
return channelConf;
};
|
¶ GameLoader.loadWaitRoomDirLoads waitroom/room.settings.js from file system and acts accordingly Synchronously looks for a file called waitroom/room.settings.js at the top level of the specified directory. Params
directory
string
The directory of the game
channel
ServerChannel
The channel object
Returns
WaitingRoom
waitRoom The created waiting room
See
ServerChannel.createWaitingRoom
|
GameLoader.prototype.loadWaitRoomDir = function(directory, channel) {
var waitRoomSettingsFile, waitRoomFile, conf, waitRoom;
var logger, gameName;
if ('string' !== typeof directory) {
throw new TypeError('GameLoader.loadWaitRoomDir: directory must be ' +
'string.');
}
logger = this.servernode.logger;
gameName = channel.gameName;
waitRoomSettingsFile = directory + 'waitroom/waitroom.settings.js';
if (!fs.existsSync(waitRoomSettingsFile)) {
logger.error('GameLoader.loadWaitRoomDir: waitroom.settings.js ' +
'not found. Game: ' + gameName + '.');
return;
}
try {
conf = require(waitRoomSettingsFile);
}
catch(e) {
logger.error('GameLoader.loadWaitRoomDir: error reading ' +
'waitroom.settings file. Game: ' + gameName + ': ' +
e.stack);
throw e;
}
if ('object' !== typeof conf) {
throw new TypeError('GameLoader.loadWaitRoomDir: room.settings file ' +
'must return a configuration object.');
}
if (conf.logicPath) {
if ('string' === typeof conf.logicPath) {
throw new TypeError('GameLoader.loadWaitRoomDir: configuration ' +
'loaded, but "logicPath" must be undefined ' +
'or string.');
}
conf.logicPath = checkLocalPath(directory, 'waitroom/', conf.logicPath);
}
else {
waitRoomFile = directory + 'waitroom/waitroom.js';
if (fs.existsSync(waitRoomFile)) {
conf.logicPath = waitRoomFile;
}
}
|
Add waiting room information to global game object. |
this.updateGameInfo(gameName, { waitroom: conf });
waitRoom = channel.createWaitingRoom(conf);
if (!waitRoom) {
throw new Error('GameLoader.loadWaitRoomDir: could not create ' +
'waiting room. Game: ' + gameName + '.');
}
return waitRoom;
};
|
¶ GameLoader.loadAuthDirReads the If a function is found in Params
file
string
The path to the configuration file
channel
ServerChannel
The channel object
See
GameLoader.getChannelSandbox
|
GameLoader.prototype.loadAuthDir = function(directory, channel) {
var authFile, authSettingsFile, authCodesFile;
var settings, codes;
var authObj, sandboxAuth, sandboxIdGen, sandboxDecorate;
var gameName;
var logger;
if ('string' !== typeof directory) {
throw new TypeError('GameLoader.loadAuthDir: directory must be ' +
'string.');
}
gameName = channel.gameName;
logger = this.servernode.logger;
|
Check if directory exists. |
if (!fs.existsSync(directory + 'auth/')) {
logger.warn('GameLoader.loadRequirementsDir: channel ' + gameName +
': no auth directory.');1
|
Add information to global game object. |
this.updateGameInfo(gameName, { auth: { enabled: false } });
return;
}
|
Auth settings. |
authSettingsFile = directory + 'auth/auth.settings.js';
if (!fs.existsSync(authSettingsFile)) {
settings = {
enabled: true,
mode: 'auto',
codes: 'auth.codes'
};
logger.warn('GameLoader.loadAuthDir: channel' + gameName +
': no auth settings. Default settings used.');
}
else {
try {
settings = require(authSettingsFile);
}
catch(e) {
throw new Error('GameLoader.loadAuthDir: cannot read ' +
authSettingsFile + ': ' + e.stack);
}
if ('object' !== typeof settings) {
throw new Error('GameLoader.loadAuthDir: invalid settings file: ' +
authSettingsFile + ': ' + e.stack);
}
}
|
Enable authorization by default. |
if ('undefined' === typeof settings.enabled) settings.enabled = true;
|
Add waiting room information to global game object. |
this.updateGameInfo(gameName, { auth: settings });
|
Exit here if auth is disabled. |
if (!settings.enabled) {
logger.warn('GameLoader.loadAuthDir: channel ' + gameName +
': authorization disabled in configuration file.');
return;
}
|
Auth codes. Can be synchronous or asynchronous. |
authCodesFile = directory + 'auth/' + (settings.codes || 'auth.codes.js');
if (fs.existsSync(authCodesFile)) {
try {
codes = require(authCodesFile)(settings, function(err, codes) {
if (err) {
throw new Error('GameLoader.loadAuthDir: an error ' +
'occurred: ' + err);
}
importAuthCodes(channel, codes, authCodesFile);
});
}
catch(e) {
throw new Error('GameLoader.loadAuthDir: cannot read ' +
authCodesFile + ": \n" + e.stack);
}
if (codes) importAuthCodes(channel, codes, authCodesFile);
}
|
Auth file. |
authFile = directory + 'auth/auth.js';
if (fs.existsSync(authCodesFile)) {
sandboxAuth = this.getChannelSandbox(channel, 'authorization');
sandboxIdGen = this.getChannelSandbox(channel, 'clientIdGenerator');
sandboxDecorate = this.getChannelSandbox(channel, 'clientObjDecorator');
authObj = {
authorization: sandboxAuth,
clientIdGenerator: sandboxIdGen,
clientObjDecorator: sandboxDecorate
};
try {
require(authFile)(authObj, settings);
}
catch(e) {
throw new Error('GameLoader.loadAuthDir: cannot read ' +
authCodesFile + '. ' + e.stack);
}
}
};
|
¶ GameLoader.loadRequirementsDirReads the Params
file
string
The path to the configuration file
channel
ServerChannel
The channel object
Returns
RequirementsRoom
The requirements room (if one is created)
|
GameLoader.prototype.loadRequirementsDir = function(directory, channel) {
var file, settingsFile, settings;
var logger, gameName;
var reqFunc, reqObj;
if ('string' !== typeof directory) {
throw new TypeError('GameLoader.loadRequirementsDir: directory ' +
'must be string.');
}
gameName = channel.gameName;
logger = this.servernode.logger;
if (!fs.existsSync(directory + 'requirements/')) {
logger.warn('GameLoader.loadRequirementsDir: channel ' + gameName +
': no requirements directory.');1
|
Add information to global game object. |
this.updateGameInfo(gameName, { requirements: { enabled: false } });
return;
}
|
Requirements settings. |
settingsFile = directory + 'requirements/requirements.settings.js';
if (!fs.existsSync(settingsFile)) {
settings = { enabled: true };
logger.warn('GameLoader.loadRequirementsDir: channel' + gameName +
': no settings file found. Default settings used.');
}
else {
try {
settings = require(settingsFile);
}
catch(e) {
throw new Error('GameLoader.loadRequirementsDir: cannot read ' +
settingsFile + ': ' + e.stack);
}
if ('object' !== typeof settings) {
throw new Error('GameLoader.loadRequirementsDir: invalid ' +
'settings file: ' + settingsFile + '.');
}
}
|
We prepend the baseDir parameter, if any. If logicPath is not string RequirementsRoom constructor throws an error. |
if ('string' === typeof settings.logicPath) {
settings.logicPath = checkLocalPath(directory, 'requirements/',
settings.logicPath);
}
|
Enable requirements by default. |
if ('undefined' === typeof settings.enabled) settings.enabled = true;
|
Exit here if requirements is disabled. |
if (!settings.enabled) {
logger.warn('GameLoader.loadRequirementsDir: channel ' + gameName +
': requirements checking disabled in configuration file.');
|
Add information to global game object. |
this.updateGameInfo(gameName, { requirements: settings });
return;
}
|
Requirements file. |
file = directory + 'requirements/requirements.js';
if (fs.existsSync(file)) {
reqObj = {};
reqFunc = new RequirementsSandbox(gameName, reqObj);
try {
require(file)(reqFunc, settings);
reqObj.enabled = true;
}
catch(e) {
throw new Error('GameLoader.loadRequirementsDir: channel ' +
gameName + ' cannot read ' + file +
'. \n' + e.stack);
}
}
|
Add information to global game object. |
this.updateGameInfo(gameName, {
requirements: J.merge(settings, reqObj)
});
|
Create requirements room in channel. |
return channel.createRequirementsRoom(J.merge(reqObj, settings));
};
|
TODO: see if we want to add it to prototype. Else remove getChannelSandbox also (maybe). |
function RequirementsSandbox(gameName, requirementsObj) {
var errBegin = 'GameLoader.loadRequirementsDir: ' + gameName + ': ';
this.add = function(cb, params) {
if ('function' !== typeof cb) {
throw new TypeError(errBegin + 'requirement must be function.');
}
if (params && 'object' !== typeof params) {
throw new TypeError(errBegin + 'params must be object or ' +
'undefined: ', params);
}
if (!requirementsObj.requirements) requirementsObj.requirements = [];
requirementsObj.requirements.push({ cb: cb, params: params });
};
this.onSuccess = function(cb) {
if ('function' !== typeof cb) {
throw new TypeError(errBegin + 'onSuccess callback must be ' +
'function.');
}
requirementsObj.onSuccess = cb;
};
this.onFailure = function(cb) {
if ('function' !== typeof cb) {
throw new TypeError(errBegin + 'onFailure callback must be ' +
'function.');
}
requirementsObj.onFailure = cb;
};
this.setMaxExecutionTime = function(maxTime) {
if ('number' !== typeof maxTime) {
throw new TypeError(errBegin + 'max execution time must be ' +
'number:' + maxTime + '.');
}
requirementsObj.maxExecTime = maxTime;
};
}
|
¶ GameLoader.getChannelSandboxReturns a sandbox function to configure a channel Params
channelName
string
The name of the game
method
string
The name of the method in ServerChannel
methodName
string
Optional The name of the method as diplayed in
error messages.
Returns
function
The sandbox
See
GameLoader.loadAuthDir
See
GameLoader.getChannelSandbox
|
GameLoader.prototype.getChannelSandbox = function(channel, method, methodName) {
var that, channelName;
that = this;
channelName = channel.name;
methodName = methodName || method;
return function() {
var len;
var callback;
var errorBegin;
var servernode;
servernode = that.servernode;
errorBegin = 'GameLoader.loadAuthDir: ' + methodName + ' callback: ';
len = arguments.length;
if (len > 2) {
throw new Error(errorBegin +
'accepts maximum 2 input parameters, ' +
len + ' given. Game: ' + channelName + '.');
}
callback = arguments[len-1];
if ('function' !== typeof callback) {
throw new TypeError(errorBegin + 'last parameter must be ' +
'function. Game: ' + channelName + '.');
}
|
Channels defined by a game with the channels.js file. Auth callback can modify the authorization only of those. |
|
1 Auth for both admin and player servers. |
if (len === 1) {
channel.adminServer[method](callback);
channel.playerServer[method](callback);
}
|
1 Auth for one server: admin or player. |
else {
if (arguments[0] !== 'admin' && arguments[0] !== 'player') {
throw new TypeError(errorBegin + 'server parameter must ' +
'be either "player" or "admin". ' +
'Given: ' + arguments[0] + '. ' +
'Game: ' + channelName + '.');
}
channel[arguments[0] + 'Server'][method](callback);
}
};
};
|
¶ GameLoader.createGameAliasAdds one or multiple game alias Adds references into the Params
alias
string
The name of the alias
gameName
string
The name of the game
See
GameLoader.addGame
See
ServerChannel.resourceManager
|
GameLoader.prototype.createGameAlias = function(alias, gameName) {
var servernode, gameInfo;
var i, len;
if ('string' === typeof alias) {
alias = [alias];
}
if (!J.isArray(alias)) {
throw new TypeError('GameLoader.createGameAlias: alias' +
'must be either string or array.');
}
if ('string' !== typeof gameName) {
throw new TypeError('GameLoader.createGameAlias: gameName must be ' +
'string.');
}
servernode = this.servernode;
gameInfo = servernode.getGamesInfo(gameName);
if (!gameInfo) {
throw new Error('GameLoader.createGameAlias: game not found: ' +
gameName);
}
i = -1;
len = alias.length;
for ( ; ++i < len ; ) {
if ('string' !== typeof alias[i]) {
throw new TypeError(
'GameLoader.createGameAlias: alias must be string.');
}
if (servernode.info.games[alias[i]]) {
throw new Error('GameLoader.createGameAlias: ' +
'alias must be unique: ' + alias[i] +
' (' + gameName + ').');
}
|
TODO: do not add aliases to the info.games object. (we need to make sure aliases do not clash). |
servernode.info.games[alias[i]] = servernode.info.games[gameName];
servernode.resourceManager.createAlias(alias[i], gameName);
gameInfo.alias.push(alias[i]);
}
};
|
¶ Helper methods |
|
¶ checkLocalPathHelper function to specify full path in a game subdir Checks if the file starts with './' and, if so, adds the absolute path to the game directory in front. Params
gameDir
string
The absolute path of the game directory
subDir
string
The name of the game sub directory
filePath
string
The path of the file in the sub directory
Returns
string
The updated file path (if local)
|
function checkLocalPath(gameDir, subDir, filePath) {
if (filePath.substring(0,2) === './') {
return path.resolve(gameDir, subDir, filePath);
}
return filePath;
}
|
¶ version2GameStageHelper function to parse a version string into a GameStage Cannot use GameStage constructor because it does not accept stage = 0 Params
str
string
The version number to parse
See
GameStage constructor
|
function version2GameStage(str) {
var tokens, stage, step, round;
var out;
tokens = str.trim().split('.');
stage = parseInt(tokens[0], 10);
if (isNaN(stage)) return false;
step = parseInt(tokens[1], 10);
if (isNaN(step)) return false;
round = parseInt(tokens[2], 10);
if (isNaN(round)) return false;
return {
stage: stage, step: step, round: round
};
}
|
¶ gameRequirementsFailHelper function that checks if game requirements are fullfilled Params
req
mixed
The game requirements from
package.json
Returns
boolean
TRUE, if check fails
See
GameStage constructor
|
function gameRequirementsFail(req) {
var reqFail;
|
|
if (!req || req.trim() === '*') return false;
|
Trick: we compare version numbers using the GameStage class. |
if (req.indexOf(">=") !== -1) {
req = req.split(">=")[1];
req = version2GameStage(req);
reqFail = !req || GameStage.compare(req, serverVersionObj) > 0;
}
else if (req.indexOf(">") !== -1) {
req = req.split(">")[1];
req = version2GameStage(req);
reqFail = !req || GameStage.compare(req, serverVersionObj) > -1;
}
else {
req = version2GameStage(req);
reqFail = !req || GameStage.compare(req, serverVersionObj) !== 0;
}
return reqFail;
}
|
¶ parseGameSettingsParses a game settings object and builds the treatments object If no All properties of the 'standard' treatment are shared with the other settings. Params
game
object
The path to a package.json file,
or its content as loaded from
require
Returns
object
out The parsed settings object containing treatments
See
GameLoader.addGame
|
function parseGameSettings(settingsObj) {
var standard, t, out;
var treatments;
if ('object' !== typeof settingsObj) {
throw new TypeError('parseGameSettings: settingsObj must be object.');
}
out = {};
standard = J.clone(settingsObj);
if (!settingsObj.treatments) {
out.standard = standard;
out.standard.treatmentName = 'standard';
out.standard.name = 'standard';
}
else {
treatments = settingsObj.treatments;
delete standard.treatments;
for (t in treatments) {
if (treatments.hasOwnProperty(t)) {
out[t] = J.merge(standard, treatments[t]);
out[t].treatmentName = t;
out[t].name = t;
}
}
}
return out;
}
|
¶ importAuthCodesImports an array of authorization codes into the channel registry Params
channel
ServerChannel
codes
array
The array of codes
file
string
The name of the file that returned the codes
(used in case of errors)
|
function importAuthCodes(channel, codes, file) {
if (!J.isArray(codes)) {
throw new Error('GameLoader.loadAuthDir: codes file must ' +
'return an array of codes, or undefined ' +
'(if asynchronous): ' + file + '.');
}
channel.registry.importClients(codes);
}
function loadSyncAsync(filepath, settings, method, cb) {
var result;
if (fs.existsSync(filepath)) {
try {
result = require(filepath)(settings, function(err, result) {
if (err) {
throw new Error('GameLoader.' + method + ': an error ' +
'occurred: ' + err);
}
cb(result);
});
}
catch(e) {
throw new Error('GameLoader.' + method + ': cannot read ' +
pwdFile + ": \n" + e.stack);
}
if (result) cb(result);
}
}
|
TODO: see if we need it in the future: |
|
¶ GameLoader.loadViewsFileLoads views.js from file system and adds channels accordingly Asynchronously looks for a file called channels.js at the top level of the specified directory. The file channels.js must export an array containing channels object in the format specified by the ServerChannel constructor. Every channel configuration object can optionally have a waitingRoom object to automatically add a waiting room to the channel. Params
directory
string
The path in which views.js will be looked for
TODO: views.js file is not loaded anymore because all context are dynamics.
See
PageManager
GameLoader.prototype.loadViewsFile = function(directory, gameInfo) { var viewsFile, that, gameName;
// TODO: see if still needed (broken now). // // that = this; // viewsFile = directory + 'views/views.js'; // fs.exists(viewsFile, function(exists) { // var cb, sb; // if (!exists) return; // cb = require(viewsFile); // if ('function' !== typeof cb) { // throw new TypeError('GameLoader.loadViewsFile: ' + // 'views.js did not ' + // 'return a valid function. Dir: ' + directory); // } // // Execute views function in a sandboxed enviroment. // // A game cannot modify other games settings. // sb = that.servernode.resourceManager // .getSandBox(gameName, directory); // cb(sb, gameInfo.settings); // }); }; |
|