## GameRoom constructor

Creates a new game room

@param {object} config Configuration options

@see Room
763590
function GameRoom(config) {
    var that, node;

    Room.call(this, 'Game', config);
    if (!this.logicPath) this.logicPath  = './rooms/game.room';

    if (config.treatmentName && 'string' !== typeof config.treatmentName) {
        throw new TypeError('GameRoom: config.treatmentName must be ' +
                            'undefined or string. Found: ' +
                            config.treatmentName);
    }

    if (config.runtimeConf && 'object' !== typeof config.runtimeConf) {
        throw new TypeError('GameRoom: config.runtimeConf must be object. ' +
                            'Found: ' + config.runtimeConf);
    }

    /**
     * ### GameRoom.treatmentName
     *
     * The name of the treatment played, or 'standard' if not defined
     */
    this.treatmentName = config.treatmentName || 'standard';

    if (!this.game.settings[this.treatmentName]) {
        throw new Error('GameRoom: game ' + this.gameName + ' treatment ' +
                        'not found: ' + this.treatmentName);
    }

    /**
     * ### GameRoom.gameLevel
     *
     * Game paths for the room
     */
    this.gameLevel = config.gameLevel;

    if (this.gameLevel && !this.game.levels[this.gameLevel]) {
        throw new Error('GameRoom: game ' + this.gameName + ' game level ' +
                        'not found: ' + this.gameLevel);
    }

    /**
     * ### GameRoom.gamePaths
     *
     * Game paths for the room
     */
    this.clientTypes = this.gameLevel ?
        this.game.levels[this.gameLevel].clientTypes :
        this.game.clientTypes;

    /**
     * ### GameRoom.gameTreatment
     *
     * Actual settings for current game based on treatment and runtime conf
     *
     * @see GameRoom.runtimeConf
     * @see GameRoom.setupGame
     */
    this.gameTreatment = this.game.settings[this.treatmentName];
    if (this.runtimeConf) J.mixin(this.gameTreatment, this.runtimeConf);

    /**
     * ### GameRoom.dataDir
     *
     * Path to the data directory
     *
     * @see ServerChannel.getGameDir
     */
    if (config.ownDataDir || this.channel.roomOwnDataDir) {
        this.dataDir = path.resolve(this.channel.getGameDir(),
                                    'data', this.name);


        // TODO: warning if it already exists.
        fs.ensureDir(this.dataDir);
    }
    else {
        this.dataDir = path.resolve(this.channel.getGameDir(), 'data');
    }
    this.dataDir += '/';

    // Configure disconnect/reconnect policy.

    that = this;
    node = this.node;

    /**
     * ## GameRoom.wrongNumOfPlayers
     *
     * Default listener for `onWronPlayerNum` on logics
     *
     * This listener is added as default stager property `onWrongPlayerNum`
     * to all `logic` client types by method `GameRoom.getClientType`,
     * if no other is already defined.
     *
     * The listener is invoked when there is a "wrong" number of players.
     * The number of players is "wrong" if it has been defined any
     * min|max|exactPlayers property in current game step.
     *
     * Default behavior:
     *
     * 1 - Immediately pauses all connected players. A message is displayed
     *     with information.
     * 2 - A timer is started for the value specified in settings.WAIT_TIME,
     *     or 30 seconds otherwise.
     * 3 - If timer expires, game is resumed and, if a custom callback is
     *     is defined, it is executed.
     *
     * The listener is then invoked with `node.game` context.
     *
     * @param {string} type The type of "wrong" number: 'min', 'max', 'exact'
     * @param {function} customCb Optional. A callback to be executed in
     *     in case the timer expires.
     * @param {object} player Optional. The player object that triggered
     *     the listener by either connecting or disconnecting (if available)
     *
     * @see GameRoom.correctNumOfPlayersAgain
     * @see GameRoom.getClientType
     */
    this.wrongNumOfPlayers = function(type, customCb, player) {
        var node;
        var waitTime, msgToClients;
        node = this.node;

        // If the default value (30) is changed the documentation
        // on the wiki needs to be updated too (Disconnections page).
        waitTime = 'number' === typeof this.settings.WAIT_TIME ?
            this.settings.WAIT_TIME : 30;

        if (type === 'min') {

            msgToClients = this.settings.WAIT_TIME_TEXT ||
                'One or more players disconnected. We are waiting ' +
                '<span id="ng_pause_timer">' + waitTime +
                '</span> seconds to see if they reconnect.';

            node.warn('warning: not enough players!!');
        }
        else if (type === 'max') {
            msgToClients = this.settings.WAIT_TIME_TEXT ||
                'Too many players connected. We are waiting ' +
                '<span id="ng_pause_timer">' + waitTime + '</span> ' +
                'seconds to see if the right number of players is restored.';

            node.warn('warning: too many players!!');
        }
        else {
            msgToClients = this.settings.WAIT_TIME_TEXT ||
                'Wrong number of connected players detected. We are waiting' +
                '<span id="ng_pause_timer">' + waitTime + '</span> ' +
                'seconds to see if the right number of players is restored.';

            node.warn('warning: wrong number of players!!');
        }

        if (!node.game.isPaused()) {
            // Pause connected players.
            node.remoteCommand('pause', 'ROOM', msgToClients);
            node.game.pause();
        }

        that.countdown = setTimeout(function() {
            node.warn('Disconnection Countdown fired: ' + node.nodename);
            node.remoteCommand('resume', 'ROOM');
            node.game.resume();
            if (customCb) customCb.call(node.game, player);
            setTimeout(function() {
                if (node.game.shouldStep()) node.game.step();
            }, 100);

        }, waitTime * 1000);
    };

    /**
     * ## GameRoom.correctNumOfPlayersAgain
     *
     * Listener invoked at any time the correct number of players is recovered
     *
     * The number of players is "wrong" if it has been defined any
     * min|max|exactPlayers property in current game step.
     *
     * Default behavior:
     *
     * 1 - Immediately resumes all connected players that have been
     *     paused by callback `wrongNumOfPlayers`
     * 2 - The timer defined by `wrongNumOfPlayers` is cleared
     * 3 - If any custom callback was defined, it gets executed
     *
     * This listener is added as default stager property `onCorrectPlayerNum`
     * to all `logic` client types by method `GameRoom.getClientType`,
     * if no othe is already defined.
     *
     * The listener is then invoked with `node.game` context.
     *
     * @param {string} type The type of "wrong" number: 'min', 'max', 'exact'
     * @param {function} customCb Optional. A callback to be executed in
     *     in case the timer expires.
     * @param {object} player Optional. The player object that triggered
     *     the listener by either connecting or disconnecting (if available)
     *
     * @see GameRoom.wrongNumOfPlayers
     * @see GameRoom.getClientType
     */
    this.correctNumOfPlayersAgain = function(type, customCb, player) {
        var node;
        node = this.node;
        if (type === 'min') {
            node.warn('warning: enough players again!!');
        }
        else if (type === 'max') {
            node.warn('warning: number of players below maximum again!!');
        }
        else {
            node.warn('warning: correct number of players again!!');
        }

        // Delete countdown to terminate the game.
        clearTimeout(that.countdown);
        that.countdown = null;

        // Player can reconnect during the countdown or after.
        // If after, then the game won't be paused.
        if (node.game.isPaused()) {
            node.game.resume();
            // Resume other players.
            setTimeout(function() {
                node.game.pl.each(function(p) {
                    if (player.id !== p.id) {
                        node.remoteCommand('resume', p.id);
                    }
                });
            }, 100);
        }

        if (customCb) customCb.call(node.game, player);
    };

    // Logs connections.
    node.on.pconnect(function(p) {
        that.logClientEvent(p, 'connect');
    });

    // Some players are already there, and do not fire pconnect.
    node.once('INIT', function(p) {
        // Avoid the whole loop.
        if (!that.logClients) return;
        node.game.pl.each(function(p) {
            that.logClientEvent(p, 'connect');
        });

    });

    // Logs disconnection.
    node.on.pdisconnect(function(p) {
        console.log('Disconnection: ' + p.id + ' (server stage: ' +
                    node.player.stage + ')');
        that.logClientEvent(p, 'disconnect');

    });

    // Player reconnecting.
    // This function is called first, and it invokes any
    // min/max/exact handler accordingly.
    node.on.preconnect(function(p) {
        var code, discoStage, discoStageLevel, curStage, reconCb, res;
        var compareSteps, reconOptions;
        var milliseconds, resetTime;

        that.logClientEvent(p, 'reconnect');

        node.warn('Oh...somebody reconnected!', p);
        code = that.channel.registry.getClient(p.id);

        discoStage = code.disconnectedStage;
        curStage = node.game.getCurrentGameStage();
        compareSteps = GameStage.compare(discoStage, curStage);

        // Check if only within stage reconnections are allowed.
        if (that.sameStepReconnectionOnly && compareSteps) {
            node.err('GameRoom: player reconnected from another step. ' +
                     'Not allowed. Player: ' + p.id);
            disposeClient(node, p);
            return;
        }

        // Setup client.
        that.setupClient(p.id);

        if (code.lang.name !== 'English') {
            // If lang is different from Eng, remote setup it.
            // TRUE: sets also the URI prefix.
            node.remoteSetup('lang', p.id, [code.lang, true]);
        }

        // Start the game on the reconnecting client.
        // Need to give step: false, we just init it.
        node.remoteCommand('start', p.id, { step: false });

        // Create the reconnection options object.
        reconOptions = { plot: {} };

        // Mark the state as DONE, if this is the case.
        // Important!
        // IF the client was DONE, and waiting for other players,
        // and the STEP command is sent right before its disconnection,
        // THEN the willBeDone option is applied to the next step, which
        // is the wrong thing to do. So we add willBeDone only on same
        // stage, or if the client is ahead.
        discoStageLevel = code.disconnectedStageLevel;
        if (compareSteps <= 0 && discoStageLevel === DONE) {
            reconOptions.willBeDone = true;
            // Temporarily remove the done callback.
            reconOptions.plot.done = null;
            reconOptions.plot.autoSet = null;
        }

        // Sets the timer on reconnecting client if milliseconds is found.
        milliseconds = node.game.timer.milliseconds;
        if ('number' === typeof milliseconds) {
            // Time left to play in stage.
            resetTime = Math.max(milliseconds -
                                 node.timer.getTimeSince('step', true), 0);

            reconOptions.plot.timer = resetTime;
        }

        reconCb = this.plot.getProperty(curStage, 'reconnect');

        if (reconCb) {
            // Res contains the reconnect options for the step,
            // or false to abort reconnection.
            res = reconCb.call(this, code, reconOptions);
            if (res === false) {
                node.warn('Reconnect Cb returned false');
                disposeClient(node, p);
                return;
            }
        }

        // Add player to player list.
        node.game.pl.add(p);

        // Start the step on reconnecting client.
        // Unless differently specified by the reconnect callback,
        // target step is what is more advanced between the logic step,
        // and the step when the client disconnected.
        if (!reconOptions.targetStep) {
            // console.log(compareSteps, curStage, code.stage);
            // -1 means first param is ahead in game stage.
            // compareSteps = GameStage.compare(code.stage, curStage);
            if (compareSteps < 0) reconOptions.targetStep = discoStage;
            else reconOptions.targetStep = curStage;
        }

        node.remoteCommand('goto_step', p.id, reconOptions);

        // See if we have enough players now.
        res = node.game.sizeManager.changeHandler('pconnect', p);

        // If we are still missing more players pause reconnecting player.
        if (!res) node.remoteCommand('pause', p.id);

    });

    this.registerRoom();
}
183812
function (config) {
        config = config || {};
        this.api_key = config.api_key;
        this.host = config.host || undefined;
        this.api_version = config.api_version || undefined;
        this.dev_mode = config.dev_mode || false;
        return this;
    }
1033769
public void create(MapTile map, Media config, SpriteTiled sheet)
    {
        this.map.create(map.getTileWidth(), map.getTileHeight(), map.getInTileWidth(), map.getInTileHeight());
        if (sheet != null)
        {
            this.map.loadSheets(Arrays.asList(sheet));
        }
        for (int i = 0; i < NO_FOG; i++)
        {
            final String group;
            if (i == FOG)
            {
                group = FOG_GROUP;
            }
            else
            {
                group = TRANSITION_GROUP;
            }
            mapGroup.changeGroup(new TileGame(Integer.valueOf(0), i, 0, 0, map.getTileWidth(), map.getTileHeight()),
                                 group);
        }
        mapGroup.changeGroup(new TileGame(Integer.valueOf(0), NO_FOG, 0, 0, map.getTileWidth(), map.getTileHeight()),
                             MapTileGroupModel.NO_GROUP_NAME);
        transition.loadTransitions(config);

        for (int x = 0; x < map.getInTileWidth(); x++)
        {
            for (int y = 0; y < map.getInTileHeight(); y++)
            {
                final Tile tile = new TileGame(Integer.valueOf(0),
                                               FOG,
                                               x * (double) map.getTileWidth(),
                                               y * (double) map.getTileHeight(),
                                               map.getTileWidth(),
                                               map.getTileHeight());
                this.map.setTile(tile);
            }
        }
    }
95007
function nodeGo(config) {
        // Create the node
        RED.nodes.createNode(this, config)

        /** @since 2019-02-02 - the current instance name (url) */
        var uibInstance = config.url // for logging

        log.verbose(`[${uibInstance}] ================ instance registered ================`)

        /** A copy of 'this' object in case we need it in context of callbacks of other functions. @constant {Object} node */
        const node = this

        log.verbose(`[${uibInstance}] = Keys: this, config =`, {'this': Object.keys(node), 'config': Object.keys(config)})

        //#region --- Create local copies of the node configuration (as defined in the .html file)
        // NB: node.id and node.type are also available
        node.name          = config.name || ''
        node.topic         = config.topic || ''
        // TODO: Needs validation as a suitable URL path
        node.url           = config.url  || 'uibuilder'
        node.fwdInMessages = config.fwdInMessages        // @since 2017-09-20 changed to remove default, || with boolean doesn't work properly
        node.allowScripts  = config.allowScripts
        node.allowStyles   = config.allowStyles
        node.debugFE       = config.debugFE
        node.copyIndex     = config.copyIndex
        node.filename      = config.filename
        node.format        = config.format
        node.template      = config.template
        //#endregion ----

        log.verbose(`[${uibInstance}] Node instance settings`, {'name': node.name, 'topic': node.topic, 'url': node.url, 'fwdIn': node.fwdInMessages, 'allowScripts': node.allowScripts, 'allowStyles': node.allowStyles, 'debugFE': node.debugFE })

        // Keep a log of the active instances @since 2019-02-02
        instances[node.id] = node.url
        log.verbose(`[${uibInstance}] Node Instances Registered`, instances)

        /** Name of the fs path used to hold custom files & folders for THIS INSTANCE of uibuilder
         *   Files in this folder are also served to URL but take preference
         *   over those in the nodes folders (which act as defaults) @type {string}
         */
        node.customFolder = path.join(uib_rootPath, node.url)

        /** User supplied vendor packages
         * & only if using dev folders (delete ~/.node-red/uibuilder/<url>/dist/index.html)
         * JK @since 2017-08-17 fix for non-existent properties and use getProps()
         * JK @since 2018-01-06 use uib_globalSettings instead of RED.settings.uibuilder. At least an empty array is returned.
         * @type {Array}
         */
        node.userVendorPackages = uiblib.getProps(RED,config,'userVendorPackages',null) || uiblib.getProps(RED,uib_globalSettings,'userVendorPackages',[])

        log.verbose(`[${uibInstance}] Node package details`, { 'usrVendorPkgs': node.userVendorPackages, 'customAppFldr': uib_rootPath, 'customFldr': node.customFolder } )

        //#region ---- Socket.IO instance configuration ----
        /** How many Socket clients connected to this instance? @type {integer} */
        node.ioClientsCount = 0
        /** How many msg's received since last reset or redeploy? @type {integer} */
        node.rcvMsgCount = 0
        /** The channel names for Socket.IO @type {Object} */
        node.ioChannels = {control: 'uiBuilderControl', client: 'uiBuilderClient', server: 'uiBuilder'}
        /** Make sure each node instance uses a separate Socket.IO namespace - WARNING: This HAS to match the one derived in uibuilderfe.js
         * @since v1.0.10, changed namespace creation to correct a missing / if httpNodeRoot had been changed from the default. @type {string} */
        node.ioNamespace = tilib.urlJoin(httpNodeRoot, node.url)
        //#endregion ---- ----

        log.verbose(`[${uibInstance}] Socket.io details`, { 'ClientCount': node.ioClientsCount, 'rcvdMsgCount': node.rcvMsgCount, 'Channels': node.ioChannels, 'Namespace': node.ioNamespace } )

        // Keep track of the number of times each instance is deployed.
        // The initial deployment = 1
        if ( deployments.hasOwnProperty(node.id) ) deployments[node.id]++
        else deployments[node.id] = 1
        log.verbose(`[${uibInstance}] Number of Deployments`, deployments[node.id] )

        // We need an http server to serve the page. @since 2019-02-04 removed httpAdmin - we only want to use httpNode for web pages
        const app = RED.httpNode // || RED.httpAdmin

        /** Provide the ability to have a ExpressJS middleware hook.
         * This can be used for custom authentication/authorisation or anything else.
         * The function must be defined in settings.js
         * @since v1.0.3 2017-12-15
         */
        var httpMiddleware = function(req,res,next) { next() }
        if ( uib_globalSettings.hasOwnProperty('middleware') ) {
            /** Is a uibuilder specific function available? */
            if ( typeof uib_globalSettings.middleware === 'function' ) {
                log.verbose(`[${uibInstance}] Using uibuilder specific middleware from settings.js`)
                httpMiddleware = uib_globalSettings.middleware
            }
        } else {
            /** If not, see if the Node-RED one is available and use that instead.
             * Use httNodeMiddleware function which is defined in settings.js
             * as for the http in/out nodes - normally used for authentication
             */
            if ( typeof RED.settings.httpNodeMiddleware === 'function' ) {
                log.verbose(`[${uibInstance}] Using Node-RED middleware from settings.js`)
                httpMiddleware = RED.settings.httpNodeMiddleware
            }
        }

        /** This ExpressJS middleware runs when the uibuilder page loads
         * @see https://expressjs.com/en/guide/using-middleware.html */
        function localMiddleware (req, res, next) {
            // Tell the client what Socket.IO namespace to use,
            // trim the leading slash because the cookie will turn it into a %2F
            res.setHeader('uibuilder-namespace', node.ioNamespace)
            res.cookie('uibuilder-namespace', tilib.trimSlashes(node.ioNamespace), {path: node.url, sameSite: true})
            next()
        }

        //#region ----- Create local folder structure ----- //
        var customStatic = function(req,res,next) { next() } // Dummy ExpressJS middleware, replaced by local static folder if needed
        var customFoldersOK = true
        // TODO: May be better as async calls - probably not, but a promisified version would be OK?
        // Make sure the global custom folder exists first
        try {
            fs.mkdirSync(uib_rootPath) // try to create
            fs.accessSync( uib_rootPath, fs.constants.W_OK ) // try to access
        } catch (e) {
            if ( e.code !== 'EEXIST' ) { // ignore folder exists error
                log.error(`[${uibInstance}] Custom folder ERROR, path: ${uib_rootPath}`, e.message)
                customFoldersOK = false
            }
        }
        // make sure the folder for this node instance exists
        try {
            fs.mkdirSync(node.customFolder)
            fs.accessSync(node.customFolder, fs.constants.W_OK)
        } catch (e) {
            if ( e.code !== 'EEXIST' ) {
                log.error(`[${uibInstance}] Local custom folder ERROR`, e.message)
                customFoldersOK = false
            }
        }
        // Then make sure the DIST & SRC folders for this node instance exist
        try {
            fs.mkdirSync( path.join(node.customFolder, 'dist') )
            fs.mkdirSync( path.join(node.customFolder, 'src') )
        } catch (e) {
            if ( e.code !== 'EEXIST' ) {
                log.error(`[${uibInstance}] Local custom dist or src folder ERROR`, e.message)
                customFoldersOK = false
            }
        }

        // We've checked that the custom folder is there and has the correct structure
        if ( customFoldersOK === true ) {
            // local custom folders are there ...
            log.debug(`[${uibInstance}] Using local front-end folders in`, node.customFolder)

            /** Now copy files from the master template folder (instead of master src) @since 2017-10-01
             *  Note: We don't copy the master dist folder
             *  Don't copy if copy turned off in admin ui 
             * TODO: always copy index.html */
            if ( node.copyIndex ) {
                const cpyOpts = {'overwrite':false, 'preserveTimestamps':true}
                fs.copy( path.join( masterTemplateFolder, templateToUse ), path.join(node.customFolder, 'src'), cpyOpts, function(err){
                    if(err){
                        log.error(`[${uibInstance}] Error copying template files from ${path.join( __dirname, 'templates')} to ${path.join(node.customFolder, 'src')}`, err)
                    } else {
                        log.debug(`[${uibInstance}] Copied template files to local src (not overwriting)`, node.customFolder )
                    }
                })
            }
        } else {
            // Local custom folders are not right!
            log.error(`[${uibInstance}] Wanted to use local front-end folders in ${node.customFolder} but could not`)
        }

        //#region Add static path for local custom files
        // TODO: need a build capability for dist - nb probably keep vendor and private code separate
        try {
            // Check if local dist folder contains an index.html & if NR can read it - fall through to catch if not
            fs.accessSync( path.join(node.customFolder, 'dist', 'index.html'), fs.constants.R_OK )
            // If the ./dist/index.html exists use the dist folder...
            log.debug(`[${uibInstance}] Using local dist folder`)
            customStatic = serveStatic( path.join(node.customFolder, 'dist') )
            // NOTE: You are expected to have included vendor packages in
            //       a build process so we are not loading them here
        } catch (e) {
            // dist not being used or not accessible, use src
            log.debug(`[${uibInstance}] Dist folder not in use or not accessible. Using local src folder`, e.message )
            customStatic = serveStatic( path.join(node.customFolder, 'src') )
            // Include vendor resource source paths if needed
            node.userVendorPackages.forEach(function (packageName) {
                // @since 2017-09-19 Using get-installed-path to find where a module is actually installed
                // @since 2017-09-19 AND try require.resolve() as backup (NB this may return unusable path for linked modules)
                var installPath = ''
                try { //@since 2017-09-21 force cwd to be NR's UserDir - Colin Law
                    installPath = getInstalledPathSync(packageName, {local:true, cwd: userDir})
                } catch (e1) {
                    // if getInstalledPath fails, try nodejs internal resolve
                    try {
                        // @since 2017-11-11 v1.0.2 resolve returns the root script not the path
                        installPath = path.dirname( require.resolve(packageName) )
                    } catch (e2) {
                        log.error(`[${uibInstance}] Failed to add user vendor path - no install found for ${packageName}.  Try doing "npm install ${packageName} --save" from ${userDir}`, e2.message );
                        RED.log.warn(`uibuilder:${uibInstance}: Failed to add user vendor path - no install found for ${packageName}.  Try doing "npm install ${packageName} --save" from ${userDir}`)
                    }
                }
                if (installPath !== '') {
                    let vendorPath = tilib.urlJoin(node.url, 'vendor', packageName)
                    log.info(`[${uibInstance}] Adding user vendor path`, {
                        'url': vendorPath, 'path': installPath
                    })
                    app.use( vendorPath, serveStatic(installPath) )
                    vendorPaths[packageName] = {'url': vendorPath, 'path': installPath}
                }
            }) // -- end of forEach vendor package -- //
        }
        //#endregion -- Added static path for local custom files -- //
        //#endregion ------ End of Create custom folder structure ------- //

        /** Create a new, additional static http path to enable
         * loading of central static resources for uibuilder */
        var masterStatic = function(req,res,next) { next() }
        //Object.defineProperty(f, 'name', {value: myName, writable: false})
        try {
            // Will we use "compiled" version of module front-end code?
            fs.accessSync( path.join(__dirname, 'dist', 'index.html'), fs.constants.R_OK )
            log.debug(`[${uibInstance}] Using master production build folder`)
            // If the ./dist/index.html exists use the dist folder...
            masterStatic = serveStatic( path.join( __dirname, 'dist' ) )
        } catch (e) {
            // ... otherwise, use dev resources at ./src/
            log.debug(`[${uibInstance}] Using master src folder and master vendor packages` )
            log.debug('        Reason for not using master dist folder: ', e.message )
            masterStatic = serveStatic( path.join( __dirname, 'src' ) )
            // Include vendor resource source paths if needed
            vendorPackages.forEach(function (packageName) {
                // @since 2017-09-19 Using get-installed-path to find where a module is actually installed
                // @since 2017-09-19 AND try require.resolve() as backup (NB this may return unusable path for linked modules)
                var installPath = ''
                try { //@since 2017-09-21 force cwd to be NR's UserDir - Colin Law
                    installPath = getInstalledPathSync(packageName, {local:true, cwd: userDir})
                } catch (e1) {
                    // if getInstalledPath fails, try nodejs internal resolve
                    try {
                        // @since 2017-11-11 v1.0.2 resolve returns the root script not the path
                        installPath = path.dirname( require.resolve(packageName) )
                    } catch (e2) {
                        log.error(`[${uibInstance}] Failed to add master vendor path - no install found for ${packageName}. Should have been installed by this module`)
                        RED.log.warn(`uibuilder:${uibInstance}: Failed to add master vendor path - no install found for ${packageName}. Should have been installed by this module`)
                    }
                }
                if (installPath !== '') {
                    log.info(`[${uibInstance}] Adding master vendor path`, {
                        'url':  tilib.urlJoin(node.url, 'vendor', packageName), 'path': installPath
                    } )
                    app.use( tilib.urlJoin(node.url, 'vendor', packageName), serveStatic(installPath) )
                }
            })
        }

        app.use( tilib.urlJoin(node.url), httpMiddleware, localMiddleware, customStatic, masterStatic )

        const fullPath = tilib.urlJoin( httpNodeRoot, node.url ) // same as node.ioNamespace

        log.info(`[${uibInstance}] Version ${nodeVersion} started at URL ${fullPath}`)
        log.info(`[${uibInstance}] UI Source files at ${node.customFolder}`)
        RED.log.info(`uibuilder:${uibInstance}: UI Builder Version ${nodeVersion} started at URL ${fullPath}`)
        RED.log.info(`uibuilder:${uibInstance}: UI Source files at ${node.customFolder}`)

        //console.dir(app._router.stack)
        //if (debug && process.env.NODE_ENV === 'development') { // Only in dev environment
            // Absolute path to output file
            //var filepath = path.join(__dirname, './routes.generated.txt')
            // Invoke express-print-routes
            //require('express-print-routes')(app, filepath)
        //}

        // We only do the following if io is not already assigned (e.g. after a redeploy)
        uiblib.setNodeStatus( { fill: 'blue', shape: 'dot', text: 'Node Initialised' }, node )

        /** Each deployed instance has it's own namespace @type {Object.ioNameSpace} */
        var ioNs = io.of(node.ioNamespace)

        /** When someone loads the page, it will try to connect over Socket.IO
         *  note that the connection returns the socket instance to monitor for responses from
         *  the ui client instance */
        ioNs.on('connection', function(socket) {
            node.ioClientsCount++

            log.verbose(`[${uibInstance}] Socket connected, clientCount: ${node.ioClientsCount}, ID: ${socket.id}`)

            uiblib.setNodeStatus( { fill: 'green', shape: 'dot', text: 'connected ' + node.ioClientsCount }, node )

            // Let the clients (and output #2) know we are connecting & send the desired debug state
            uiblib.sendControl({
                'uibuilderCtrl': 'client connect',
                'cacheControl': 'REPLAY',          // @since 2017-11-05 v0.4.9 @see WIKI for details
                'debug': node.debugFE,
                '_socketId': socket.id,
                'from': 'server',
                // @since 2018-10-07 v1.0.9 - send server timestamp so that client can work out
                // time difference (UTC->Local) without needing clever libraries.
                'serverTimestamp': (new Date()),
            }, ioNs, node)
            //ioNs.emit( node.ioChannels.control, { 'uibuilderCtrl': 'server connected', 'debug': node.debugFE } )

            // if the client sends a specific msg channel...
            socket.on(node.ioChannels.client, function(msg) {
                log.debug(`[${uibInstance}] Data received from client, ID: ${socket.id}, Msg:`, msg)

                // Make sure the incoming msg is a correctly formed Node-RED msg
                switch ( typeof msg ) {
                    case 'string':
                    case 'number':
                    case 'boolean':
                        msg = { 'topic': node.topic, 'payload': msg}
                }

                // If the sender hasn't added msg._clientId, add the Socket.id now
                if ( ! msg.hasOwnProperty('_socketId') ) {
                    msg._socketId = socket.id
                }

                // Send out the message for downstream flows
                // TODO: This should probably have safety validations!
                node.send(msg)
            })
            socket.on(node.ioChannels.control, function(msg) {
                log.debug(`[${uibInstance}] Control Msg from client, ID: ${socket.id}, Msg:`, msg)

                // Make sure the incoming msg is a correctly formed Node-RED msg
                switch ( typeof msg ) {
                    case 'string':
                    case 'number':
                    case 'boolean':
                        msg = { 'uibuilderCtrl': msg }
                }

                // If the sender hasn't added msg._clientId, add the Socket.id now
                if ( ! msg.hasOwnProperty('_socketId') ) {
                    msg._socketId = socket.id
                }

                // @since 2017-11-05 v0.4.9 If the sender hasn't added msg.from, add it now
                if ( ! msg.hasOwnProperty('from') ) {
                    msg.from = 'client'
                }

                // Send out the message on port #2 for downstream flows
                uiblib.sendControl(msg, ioNs, node)  // fn adds topic if needed
                //node.send([null,msg])
            })

            socket.on('disconnect', function(reason) {
                node.ioClientsCount--
                log.debug(
                    `[${uibInstance}] Socket disconnected, clientCount: ${node.ioClientsCount}, Reason: ${reason}, ID: ${socket.id}`
                )
                if ( node.ioClientsCount <= 0) uiblib.setNodeStatus( { fill: 'blue', shape: 'dot', text: 'connected ' + node.ioClientsCount }, node )
                else uiblib.setNodeStatus( { fill: 'green', shape: 'ring', text: 'connected ' + node.ioClientsCount }, node )
                // Let the control output port know a client has disconnected
                uiblib.sendControl({
                    'uibuilderCtrl': 'client disconnect',
                    'reason': reason,
                    '_socketId': socket.id,
                    'from': 'server'
                }, ioNs, node)
                //node.send([null, {'uibuilderCtrl': 'client disconnect', '_socketId': socket.id, 'topic': node.topic}])
            })

            socket.on('error', function(err) {
                log.error(`[${uibInstance}] ERROR received, ID: ${socket.id}, Reason: ${err.message}`)
                // Let the control output port know there has been an error
                uiblib.sendControl({
                    'uibuilderCtrl': 'socket error',
                    'error': err.message,
                    '_socketId': socket.id,
                    'from': 'server'
                }, ioNs, node)
            })

            /* More Socket.IO events but we really don't need to monitor them
                socket.on('disconnecting', function(reason) {
                    RED.log.audit({
                        'UIbuilder': node.url+' DISCONNECTING received', 'ID': socket.id,
                        'data': reason
                    })
                })
                socket.on('newListener', function(data) {
                    RED.log.audit({
                        'UIbuilder': node.url+' NEWLISTENER received', 'ID': socket.id,
                        'data': data
                    })
                })
                socket.on('removeListener', function(data) {
                    RED.log.audit({
                        'UIbuilder': node.url+' REMOVELISTENER received', 'ID': socket.id,
                        'data': data
                    })
                })
                socket.on('ping', function(data) {
                    RED.log.audit({
                        'UIbuilder': node.url+' PING received', 'ID': socket.id,
                        'data': data
                    })
                })
                socket.on('pong', function(data) {
                    RED.log.audit({
                        'UIbuilder': node.url+' PONG received', 'ID': socket.id,
                        'data': data
                    })
                })
            */

        }) // ---- End of ioNs.on connection ---- //

        /** Handler function for node input events (when a node instance receives a msg)
         * @param {Object} msg The msg object received.
         **/
        function nodeInputHandler(msg) {
            log.verbose(`[${uibInstance}] nodeGo:nodeInputHandler - emit received msg - Namespace: ${node.url}`) //debug

            // If msg is null, nothing will be sent
            if ( msg !== null ) {
                // if msg isn't null and isn't an object
                // NOTE: This is paranoid and shouldn't be possible!
                if ( typeof msg !== 'object' ) {
                    // Force msg to be an object with payload of original msg
                    msg = { 'payload': msg }
                }
                // Add topic from node config if present and not present in msg
                if ( !(msg.hasOwnProperty('topic')) || msg.topic === '' ) {
                    if ( node.topic !== '' ) msg.topic = node.topic
                    else msg.topic = 'uibuilder'
                }
            }

            // Keep this fn small for readability so offload
            // any further, more customised code to another fn
            msg = uiblib.inputHandler(msg, node, RED, io, ioNs, log)

        } // -- end of msg received processing -- //

        // Process inbound messages
        node.on('input', nodeInputHandler)

        // Do something when Node-RED is closing down
        // which includes when this node instance is redeployed
        node.on('close', function(removed,done) {
            log.debug(`[${uibInstance}] nodeGo:on-close: ${removed?'Node Removed':'Node (re)deployed'}`)

            node.removeListener('input', nodeInputHandler)

            // Do any complex close processing here if needed - MUST BE LAST
            //processClose(null, node, RED, ioNs, io, app) // swap with below if needing async
            uiblib.processClose(done, node, RED, ioNs, io, app, log, instances)

            done()
        })

    }
1233529
function ServerChannel(options, servernode, sio) {

    /**
     * ### ServerChannel.servernode
     *
     * Reference to the ServerNode
     */
    this.servernode = servernode;

    /**
     * ### ServerChannel.sio
     *
     * Reference to the Socket.IO app of ServerNode
     */
    this.sio = sio;

    /**
     * ### ServerChannel.options
     *
     * Configuration options
     *
     * // TODO: do not parse options
     * // (they are already parsed by ServerNode).
     */
    this.options = ServerChannel.parseOptions(options);

    /**
     * ### ServerChannel.name
     *
     * The name of the channel (under normal condition equal to gameName)
     */
    this.name = options.name;

    /**
     * ### ServerChannel.gameName
     *
     * The name of the game associated with the channel
     */
    this.gameName = options.gameName;

    /**
     * ### ServerChannel.defaultChannel
     *
     * TRUE, if this is the ServerNode's default Channel.
     */
    this.defaultChannel = null;

    /**
     * ### ServerChannel.gameInfo
     *
     * Reference to the game information, including treatments and client types
     */
    this.gameInfo = this.servernode.info.games[this.gameName];

    /**
     * ### ServerChannel.sysLogger
     *
     * The logger for the channel
     */
    this.sysLogger = Logger.get('channel', {name: this.name});

    /**
     * ### ServerChannel.session
     *
     * Unique session id, shared by both Player and Admin Server
     */
    this.session = '' + Math.floor(Math.random()*10000000000000000);

    /**
     * ## ServerChannel.credentials
     *
     * Administrator account to manage the channel
     *
     * The `GameLoader` will populate the object with the data read from
     * file system. The final object is of the type:
     *
     *  - {user: 'admin', pwd: 'pwd' }
     *
     * @see GameLoader.loadServerDir
     */
    this.credentials = null;

    /**
     * ### ServerChannel._loadingCredentials
     *
     * Flags that credentials are being loaded async
     */
    this._loadingCredentials = null;

    /**
     * ### ServerChannel.session
     *
     * Secret string used to sign channels' json tokens
     *
     * The `GameLoader` will read the secret from file system.
     *
     * @see GameLoader.loadServerDir
     * @see ServerChannel._loadingSecret
     */
    this.secret = null;

    /**
     * ### ServerChannel._loadingSecret
     *
     * Flags that secret is being loaded async
     */
    this._loadingSecret = null;

    /**
     * ### ServerChannel.registry
     *
     * The registry of all connected clients
     *
     * @see ChannelRegistry
     */
    this.registry = new ChannelRegistry({
        channelName: this.name
    });

    /**
     * ### ServerChannel.gameRouter
     *
     * Creates and handles routes from public/ and views/
     *
     * @see GameRouter
     */
    this.gameRouter = new GameRouter(this);

    /**
     * ### ServerChannel.admin
     *
     * The admin server
     *
     * @see GameServer
     * @see AdminServer
     */
    this.adminServer = null;

    /**
     * ### ServerChannel.player
     *
     * The player server
     *
     * @see GameServer
     * @see PlayerServer
     */
    this.playerServer = null;

    /**
     * ### ServerChannel.garageRoom
     *
     * The garage room for the channel
     */
    this.garageRoom = null;

    /**
     * ### ServerChannel.waitingRoom
     *
     * The waiting room for the channel
     *
     * @see ServerChannel.createWaitingRoom
     */
    this.waitingRoom = null;

    /**
     * ### ServerChannel.requirementsRoom
     *
     * The requirements room for the channel
     *
     * @see ServerChannel.createRequirementsRoom
     */
    this.requirementsRoom = null;

    /**
     * ### ServerChannel.gameRooms
     *
     * Associative container for all the game rooms in the channel
     *
     * @see ServerChannel.createGameRoom
     */
    this.gameRooms = {};

    /**
     * ### ServerChannel.maxRooms
     *
     * The maximum number of game rooms allowed in this channel
     *
     * The value -1 means unlimited.
     */
    this.maxRooms = 'undefined' === typeof options.maxRooms ?
        -1 : options.maxRooms;

    /**
     * ### ServerChannel.autoRoomNo
     *
     * Incremental index used when creating game rooms with a default name
     */
    if ('undefined' === typeof options.roomCounter) {
        this.autoRoomNo = 0;
    }
    else {
        this.autoRoomNo = options.roomCounter;
    }

    /**
     * ### ServerChannel.roomOwnDataDir
     *
     * If TRUE, each new room is assigned an own data dir
     */
    this.roomOwnDataDir = 'undefined' === typeof options.roomOwnDataDir ?
        true : !!options.roomOwnDataDir;

    /**
     * ### ServerChannel.logClients
     *
     * If TRUE, each room connection/disconnection is logged
     *
     * Note: rooms can overwrite this setting.
     *
     * Default: FALSE
     */
    this.logClients = options.logClients || false;

    /**
     * ### ServerChannel.logClientsExtra
     *
     * A callback adding extra information for every client log
     *
     * Note: rooms can overwrite this setting.
     *
     * Callback takes the full client object and must return an array
     * of strings to be serialized.
     *
     * Default: NULL
     *
     * @see ServerChannel.logClients
     */
    this.logClientsExtra = options.logClientsExtra || null;

    /**
     * ### ServerChannel.logClientsInterval
     *
     * How often in milliseconds clients log are dumped to file system
     *
     * Note: rooms can overwrite this setting.
     *
     * Default: 1000
     *
     * @see ServerChannel.logClients
     */
    this.logClientsInterval = options.logClientsInterval || 1000;

    /**
     * ### ServerChannel.roomCounterPadChars
     *
     * The number of chars for the roomCounter when saved as a directory on fs
     *
     * If room counter is less than roomCounterPadChars,
     * leading zeros are added.
     *
     * @see padFileNameNumber
     */
    if ('undefined' === typeof options.roomCounterPadChars) {
        this.roomCounterPadChars = 6;
    }
    else {
        this.roomCounterPadChars = options.roomCounterPadChars;
    }

    /**
     * ### ServerChannel.roomCounterSeparator
     *
     * A character to be inserted between 'room' and the room counter.
     *
     * This feature is not implemented now, and reserved for future versions.
     */
    this.roomCounterSeparator = '';
    //     if ('undefined' === typeof options.roomCounterSeparator) {
    //         this.roomCounterSeparator = '';
    //     }
    //     else {
    //         this.roomCounterSeparator = options.roomCounterSeparator;
    //     }

    /**
     * ### ServerChannel.gameLevels
     *
     * Active game levels of the game
     */
    this.gameLevels = {};

    /**
     * ### ServerChannel.bots
     *
     * node instances of bots inside the channel, indexed by Client ID
     */
    this.bots = {};

    /**
     * ### ServerChannel.phantoms
     *
     * Spawn phantomjs processes, indexed by PID
     */
    this.phantoms = {};

    // Creating the AdminServer and PlayerServer.
    this.createServers();

    // Create garage room.
    this.garageRoom = this.createRoom('Garage');
}
59901
function(config) {
      tesselKitConfig = config === undefined || config === null ? {} : config;
      module.exports.tesselKitConfig = tesselKitConfig;

      module.exports.generalUtils = require("./general-utils");
      module.exports.logHelper = require("./log-helper");
      module.exports.tesselUtils = require("./tessel-utils");

      module.exports.ButtonMgr = require("./classes/button-mgr");
      module.exports.LedMgr = require("./classes/led-mgr");

      return this;
    }
1114935
function SessionManager(config) {
      this.start = __bind(this.start, this);
      this.storeKey = __bind(this.storeKey, this);
      this.config = extend(this.defaults.config, config);
      this.RedisClient = redis.createClient(config.redis.port, config.redis.host, config.redis);
    }
1248582
@Override
  public synchronized void init(PlayerConfig config) {
    super.init(config);
    String dictFileName = config.get(DICTIONARY_FILE_NAME_KEY);
    if (dictFileName == null || "".equals(dictFileName.trim())) {
      throw new IllegalArgumentException(this.getClass().getName() + " expects configuration property "
          + DICTIONARY_FILE_NAME_KEY);
    }
    File f = new File(dictFileName);
    if (!f.exists() || f.isDirectory()) {
      throw new IllegalArgumentException("Property " + DICTIONARY_FILE_NAME_KEY
          + " should designate existing dictionary file!");
    }
    BufferedReader bufferedReader = null;
    try {
      bufferedReader = new BufferedReader(new FileReader(f));
      while (true) {
        String phrase = bufferedReader.readLine();
        if (phrase != null) {
          DICTIONARY.add(phrase.trim());
        } else {
          break;
        }
      }
    } catch (IOException ioe) {
      throw new IllegalArgumentException("File " + f.getName() + " under key " + DICTIONARY_FILE_NAME_KEY
          + " not readable!");
    } finally {
      try {
        if (bufferedReader != null)
        bufferedReader.close();
      } catch (IOException ioe) {
        throw new RuntimeException(ioe);
      }
    }
  }
469130
function initMongo(config, callback) {
  
  var self = this;
  
  // Set db
  self.db = new Db(config.database, new Server(config.host, config.port, {}), {safe: true});
  
  // Get client
  self.db.open(function(err, client) {
    if (err) throw err;
    else {
      self.client = client;
      client.collectionNames(function(err, names) {
        if (err) throw err;
        else {
          var name = config.database + '.' + config.collection;
          var exists = names.filter(function(col) {
            if (col.name == name) return col; 
          });
          
          // Collection options
          var opts = {capped: true, size: config.logSize};
          if (config.logLimit != null) opts.max = config.logLimit;
          
          if (exists.length === 0) {
            client.createCollection(config.collection, opts, function(err, collection) {
              if (err) throw err;
              else {
                self.collection = collection;
                callback.call(self);
              }
            });
          } else {
            // Get collection
            client.collection(config.collection, function(err, collection) {
              if (err) throw err;
              else {
                self.collection = collection;
                // Get collection options » Check if collection has already been capped
                collection.options(function(err, options) {
                  if (err) {
                    throw err;
                  } else {
                    // Check if collection's options match log requirements
                    var ready = (options.capped === true && options.size === opts.size);
                    if ('max' in opts) ready = (ready && options.max === opts.max);
                    if (!ready) {
                      // Convert collection to capped if it's not match log requirements
                      var cmd = {"convertToCapped": config.collection, size: opts.size};
                      if ('max' in opts) cmd.max = opts.max;
                      client.command(cmd, function(err) {
                        if (err) throw err;
                        else {
                          callback.call(self);
                        }
                      });
                    } else {
                      callback.call(self);
                    }
                  }
                });
              }
            });
          }
        }
      });
    }
  });
}
576157
function INIT(config) {
  if (__init) return;

  CONF = config

  if (!! CONF.redis) {
    // TODO: avoid this hassle if https://github.com/LearnBoost/kue/issues/54 
    //       ever gets fixed
    reds.createClient = function() {
      var client = redis.createClient(CONF.redis.port, CONF.redis.host)
      client.auth(CONF.redis.pass)
      return client
    }

    kue.redis.createClient = reds.createClient
  }

  JOBS = kue.createQueue()
  RED = JOBS.client

  JOBS.promote()

  __init = true
}
968688
function (config, options) {
    this.config = config;
    this.options = options;
    this.channel = null;
    this._status = CONNECT_STATUS.unconnected;
}
493785
function WaitingRoom(config) {
    // Supercall
    HostRoom.call(this, 'Waiting', config);
    if (!this.logicPath) this.logicPath = ngt.resolve('waitroom/waitroom.js');

    /**
     * ### WaitingRoom.roomOpen
     *
     * Flag indicating whether the room is open
     */
    this.roomOpen = config.roomOpen || true;

    /**
     * ### WaitingRoom.dispatching
     *
     * Numeric flag indicating whether the room is dispatching a new game
     *
     *
     */
    this.dispatching = 0;

    /**
     * ### WaitingRoom.timeOuts
     *
     * Array of setTimeout return values indexed by playerIDs
     */
    this.timeOuts = {};

    /**
     * ### WaitingRoom.notifyTimeout
     *
     * A timeout to notify players about updates in the number of players
     *
     * @see WaitingRoom.notifyPlayers
     */
    this.notifyTimeout = null;

    /**
     * ### WaitingRoom.numberOfDispatches
     *
     * Non-negative integer indicating the number of game dispatches
     */
    this.numberOfDispatches = config.numberOfDispatches || 0;

    /**
     * ### WaitingRoom.lastGameRoom
     *
     * Reference to the last created game room
     */
    this.lastGameRoom = null;

    /**
     * ### WaitingRoom.GROUP_SIZE
     *
     * Positive non-zero integer indicating the size of each group
     */
    this.GROUP_SIZE = null;

    /**
     * ### WaitingRoom.POOL_SIZE
     *
     * Positive non-zero integer indicating number of clients for group forming
     */
    this.POOL_SIZE = null;

    /**
     * ### WaitingRoom.DISCONNECT_IF_NOT_SELECTED
     *
     * Boolean or null, indicating whether unselected players are disconnected
     */
    this.DISCONNECT_IF_NOT_SELECTED = null;

    /**
     * ### WaitingRoom.timeOuts
     *
     * Array of setTimeout return values indexed by playerIDs
     */
    this.N_GAMES = null;

    /**
     * ### WaitingRoom.START_DATE
     *
     * Time and date of game start
     *
     * Must be valid input for `Date` constructor
     */
    this.START_DATE = null;

    /**
     * ### WaitingRoom.EXECUTION_MODE
     *
     * String indicating execution mode
     *
     * In the execution mode `'TIMEOUT`, one waits until the time is up, then
     * it will be checked whether enough players are there to start the game.
     * In the execution mode `WAIT_FOR_N_PLAYERS`, the game starts right away
     * if there are the desired number of players. Otherwise, when the time is
     * up, it will be checked if there are at least a certain minimum number of
     * players to start the game. If the execution mode is `WAIT_FOR_DISPATCH`,
     * it simply waits for a dispatch command from monitor, the number of
     * connected players is not taken into account.
     */
    this.EXECUTION_MODE = null;

    /**
     * ### WaitingRoom.MAX_WAIT_TIME
     *
     * Positive non-zero integer indicating max number of milliseconds to start
     *
     * Cannot be defined at the same time as `WaitingRoom.START_DATE`
     */
    this.MAX_WAIT_TIME = null;

    /**
     * ### WaitingRoom.ON_TIMEOUT
     *
     * Callback to be executed client-side after waiting time has elapsed
     */
    this.ON_TIMEOUT = null;

    /**
     * ### WaitingRoom.ON_TIMEOUT_SERVER
     *
     * Callback to be executed after a timeout has expired
     */
    this.ON_TIMEOUT_SERVER = null;

    /**
     * ### WaitingRoom.ON_OPEN
     *
     * Callback to be executed when the waiting room becomes open
     */
    this.ON_OPEN = null;

    /**
     * ### WaitingRoom.ON_CLOSE
     *
     * Callback to be executed when the waiting room becomes close
     */
    this.ON_CLOSE = null;

    /**
     * ### WaitingRoom.ON_CONNECT
     *
     * Callback to be executed when a player connects
     */
    this.ON_CONNECT = null;

    /**
     * ### WaitingRoom.ON_DISCONNECT
     *
     * Callback to be executed when a player disconnects
     */
    this.ON_DISCONNECT = null;

    /**
     * ### WaitingRoom.ON_INIT
     *
     * Callback to be executed after the settings have been parsed
     */
    this.ON_INIT = null;

    /**
     * ### WaitingRoom.ON_DISPATCH
     *
     * Callback to be executed just before starting dispatching
     *
     * Receives the options of the dispatch call as second param
     */
    this.ON_DISPATCH = null;

    /**
     * ### WaitingRoom.ON_DISPATCHED
     *
     * Callback to be executed at the end of a dispatch call
     */
    this.ON_DISPATCHED = null;

    /**
     * ### WaitingRoom.ON_FAILED_DISPATCH
     *
     * Callback to be executed if a dispatch attempt failed
     */
    this.ON_FAILED_DISPATCH = null;

    /**
     * ### WaitingRoom.CHOSEN_TREATMENT
     *
     * String or undefined indicating chosen treatment
     */
    this.CHOSEN_TREATMENT = null;

    /**
     * ### WaitingRoom.OVERWRITE_CHOSEN_TREATMENT
     *
     * If set, it overwrites chosen treatment for the next dispatch only
     *
     * When dispatch is invoked remotely, users may specify a treatment.
     *
     * It is set to null at every dispatch.
     *
     * @see WaitingRoom.ALLOW_SELECT_TREATMENT
     */
    this.OVERWRITE_CHOSEN_TREATMENT = null;

    /**
     * ### WaitingRoom.PLAYER_SORTING
     *
     * Callback to sort the list of players before dispatching
     *
     * If NULL, random sorting takes place. Default: 'timesNotSelected'
     */
    this.PLAYER_SORTING = WaitingRoom.sortingCallbacks.timesNotSelected;

    /**
     * ### WaitingRoom.PING_BEFORE_DISPATCH
     *
     * If TRUE, all players are pinged before a dispatch (Default: TRUE)
     *
     * Non-responding clients are disconnected.
     *
     * If only one player is needed and mode is 'WAIT_FOR_N_PLAYERS',
     * pinging is skipped.
     *
     * @see PING_MAX_REPLY_TIME
     */
    this.PING_BEFORE_DISPATCH = true;

    /**
     * ### WaitingRoom.PING_MAX_REPLY_TIME
     *
     * The time to wait to receive a ping back
     *
     * Clients will be disconnected if they do not reply in time.
     *
     * @see PING_BEFORE_DISPATCH
     */
    this.PING_MAX_REPLY_TIME = 3000;

    /**
     * ### WaitingRoom.PING_DISPATCH_ANYWAY
     *
     * If TRUE, dispatch continues even if disconnections occur during PING
     *
     * @see PING_BEFORE_DISPATCH
     * @see PING_MAX_REPLY_TIME
     */
    this.PING_DISPATCH_ANYWAY = false;

    /**
     * ### WaitingRoom.TEXTS
     *
     * List of texts replacing default ones in WaitingRoom widget
     *
     * @see WaitingRoom.texts (widget)
     */
    this.TEXTS = {};

    /**
     * ### WaitingRoom.SOUNDS
     *
     * List of paths to sounds replacing default ones in WaitingRoom widget
     *
     * @see WaitingRoom.sounds (widget)
     */
    this.SOUNDS = {};

    /**
     * ### WaitingRoom.PAGE_TITLE
     *
     * Sets the page title, optionally adds to page
     *
     */
    this.PAGE_TITLE =  { title: 'Welcome!', addToBody: true };

    /**
     * ### WaitingRoom.isPingInProgress
     *
     * Flag to indicate whether currently pinging for unresponsive players
     */
    this.isPingInProgress = false;

    /**
     * ### WaitingRoom.suspendedPings
     *
     * Flag to indicate whether currently pinging for unresponsive players
     */
    this.suspendedPings = [];

    /**
     * ### WaitingRoom.closeAfterDispatch
     *
     * Flag that the waiting room will close after next dispatch
     *
     * Note: waiting room can dispatch multiple groups, and then will close.
     */
    this.closeAfterDispatch = false;

    /**
     * ### WaitingRoom.makeClientTimeouts
     *
     * TRUE, if it makes timeouts for connecting clients
     *
     * Makes timeout if START_DATE or MAX_WAIT_TIME is set.
     *
     * @see WaitingRoom.makeTimeOut
     * @see WaitingRoom.clearTimeOut
     * @see WaitingRoom.START_DATE
     * @see WaitingRoom.MAX_WAIT_TIME
     */
    this.makeClientTimeOuts = false;

    /**
     * ### WaitingRoom.NOTIFY_INTERVAL
     *
     * The number of milliseconds to wait before sending a player-update message
     *
     * @see WaitingRoom.notifyPlayerUpdate
     */
    this.NOTIFY_INTERVAL = 200;

    /**
     * ### WaitingRoom.availableTreatments
     *
     * List of available treatments including treatment callbacks
     *
     * Maps names to descriptions.
     */
    this.availableTreatments = (function(that) {
        var o, t, key;
        if (that.gameLevel) {
            // Treatments are not yet parsed.
            t = that.game.levels[that.gameLevel].settings.treatments;
        }
        else {
            // Treatments are already parsed at this point.
            t = that.game.settings;
        }
        // Hard-cloned, check names in treatmentCallbacks.
        o = {
            treatment_random:
            WaitingRoom.treatmentCallbacks['treatment_random'],
            treatment_rotate:
            WaitingRoom.treatmentCallbacks['treatment_rotate']
        };
        for (key in t) {
            if (t.hasOwnProperty(key)) {
                o[key] = t[key].description || 'no description';
            }
        }
        return o;
    })(this);
}
1275860
function createSession(config, cb) {
  checkOpts(config, cb)

  cb = utils.callOnce(cb)

  var url = config.url
  var key = config.key

  config = {
    url: url,
    key: key
  }

  session.createSession(config, cb)
}
703991
function (config) {
			var _config = objectAssign({}, { wait: true }, config);
			this.navigate();
			if (_config.wait) this.waitForHomeScreen();
			return this;
		}
16788
function (config) {
    config = config || {};
    Appender.call(this, config);

    /**
     * True to log strings instead of objects
     */
    this.appendStrings = (typeof config.appendStrings !== 'undefined') ?
      config.appendStrings :
      true;
  }
300571
private CreateRoomSettings createRoomSettings(ApiRoom agent) {
        validateRoomAgentClass(agent);
	    CreateRoomSettings settings = new CreateRoomSettings();
        settings.setName(agent.getName());
        settings.setPassword(agent.getPassword());
        settings.setDynamic(agent.isDynamic());
        settings.setGame(agent.isGame());
        settings.setHidden(agent.isHidden());
        settings.setMaxSpectators(agent.getMaxSpectators());
        settings.setMaxUsers(agent.getMaxUsers());
        settings.setMaxVariablesAllowed(agent.getMaxRoomVariablesAllowed());
        settings.setRoomProperties(agent.getProperties());
        settings.setGroupId(agent.getGroupdId());
        settings.setUseWordsFilter(agent.isUseWordsFilter());
        settings.setAutoRemoveMode(SFSRoomRemoveMode.fromString(agent.getRemoveMode().name()));
        if(agent.getExtension() != null) 
            settings.setExtension(new RoomExtensionSettings(agent.getExtension().getName(), agent.getExtension().getClazz()));
        return settings;
	}
1320134
function (className,config)
		{
			var component = this.createClass(className,config);
			if (component)
				component.setParent(this);
			return component;
		}