<commit_before>'use strict';
//debugger;

/**
 * @file GameEngine class. Part of the MurmuresJS project.
 * @author github.com/azziliz
 * @author github.com/thyshimrod
 * @license MIT
 */

/**
 * GameEngine is the main manager for all game objects.
 *
 * A single instance of this class is created when Node starts and is kept alive at all time afterwards.
 * This instance is in the global scope and can be accessed from any other class.
 * This is the only variable of the murmures project in the global scope.
 * During startup, the server loads all references data -stored in JSON files- into the engine instance.
 * This includes assets (list of all physical bodies and character templates), locale files, heroes and static levels.
 *
 * This class is also in charge of applying client orders to the game and calling AI methods.
 *
 * @class
 */
murmures.GameEngine = function () {
    /* No guid on GameEngine ; we just want one */
    /** @type {number} */
    this.tileSize = 32 | 0;
    /** @type {Object.<string, murmures.PhysicalBody>} */
    this.bodies = {};
    /** @type {Object.<string, Object.<string, string>>} */
    this.locale = {};
    /** @type {murmures.Level} */
    this.level = {};
    /** @type {Array.<murmures.Character>} */
    this.heros = [];
    /** @type {Array.<murmures.TurnReport>} */
    this.reportQueue = [];

    /* Server-only */
    /** @type {Array.<murmures.Level>} */
    this.levels = {};
    /** @type {Array.<string>} */
    this.levelIds = {};
    /** @type {number} */
    this.activeLevel = 0 | 0;
    /** @type {number} */
    this.gameTurn = 0 | 0;
    /** @type {number} */
    this.state = murmures.C.STATE_ENGINE_INIT | 0;
    /** @type {Array.<murmures.Order>} */
    this.orderQueue = [];
};

murmures.GameEngine.prototype = {

    /*
     * No build method here because initialization involves several Node-only functions.
     * We don't want to expose these functions to the client because they don't exist there.
     * Instead, we build the gameEngine master instance in server.js
     */

    /**
     * Synchronization method called on client side only.
     * Creates a full GameEngine objects from a JSON sent by the server.
     * Sub-classes are also synchronized recursively.
     * This function expects a full GameEngine object as input and is intended to overwrite the client instance completely.
     *
     * @param {Object} src - A parsed version of the stringified remote gameEngine instance.
     */
    initialize : function (src) {
        this.tileSize = src.tileSize;
        this.bodies = src.bodies;
        this.locale = src.locale;
        this.levelIds = src.levelIds;
        this.level = new murmures.Level();
        this.level.initialize(src.level);
        this.heros = [];
        src.heros.forEach(function (hero) {
            let tempHero = new murmures.Character();
            tempHero.initialize(hero);
            this.heros.push(tempHero);
        }, this);
        this.reportQueue = [];
        if (typeof src.reportQueue !== 'undefined') {
            src.reportQueue.forEach(function (report) {
                let tempReport = new murmures.TurnReport();
                tempReport.initialize(report);
                this.reportQueue.push(tempReport);
            }, this);
        }
        this.state = src.state;
    },

    /**
     * Synchronization method called on client side only.
     * This function receives a partial GameEngine as input and merges it into the client instance.
     */
    synchronize: function (src) {
        if (typeof src === 'undefined') return;
        let isNewLevel = (typeof src.level !== 'undefined') && (typeof src.level.guid !== 'undefined') && (this.level.guid !== src.level.guid);
        if (isNewLevel) {
            this.level = new murmures.Level();
            this.level.initialize(src.level);
        } else {
            this.level.synchronize(src.level);
        }
        if (src.state !== 'undefined') {
            this.state = src.state;
        }
        if (typeof src.heros !== 'undefined') {
            src.heros.forEach(function (remoteHero) {
                this.heros.forEach(function (localHero) {
                    if (localHero.guid === remoteHero.guid) {
                        localHero.synchronize(remoteHero);
                    }
                }, this);
            }, this);
        }
        this.reportQueue = [];
        if (typeof src.reportQueue !== 'undefined') {
            src.reportQueue.forEach(function (report) {
                let tempReport = new murmures.TurnReport();
                tempReport.initialize(report);
                this.reportQueue.push(tempReport);
            }, this);
        }
    },

    clone : function (src) {
        let tempHeros = [];
        for (let itHero = 0; itHero < this.heros.length ; itHero++) {
            let hero = this.heros[itHero].clone();
            tempHeros.push(hero);
        }
        return {
            state: this.state,
            level: this.level.clone(),
            heros: tempHeros
        };
    },

    compare : function (beforeState) {
        let ret = {};
        if (this.state != beforeState.state) {
            ret.state = this.state;
        }
        let level_ = this.level.compare(beforeState.level);

        if (typeof level_ !== 'undefined') ret.level = level_;
        let heros_ = [];
        for (let itHero = 0; itHero < this.heros.length; itHero++) {
            for (let itHero_ = 0; itHero_ < beforeState.heros.length; itHero_++) {
                if (beforeState.heros[itHero_].guid === this.heros[itHero].guid) {
                    let hero_ = this.heros[itHero].compare(beforeState.heros[itHero_]);
                    if (typeof hero_ !== 'undefined') heros_.push(hero_);
                }
            }
        }
        if (heros_.length > 0) {
            ret.heros = heros_;
        }
        if (this.reportQueue.length > 0) {
            ret.reportQueue = this.reportQueue;
        }
        for (let prop in ret) {
            // only returns ret if not empty
            return ret;
        }
        // otherwise, no return = undefined
    },

    getHeroByGuid: function (guid) {
        this.heros.forEach(function (hero) {
            if (hero.guid === guid) return hero;
        }, this);
    },

    /**
     * This function is called on client and server side.
     * If the order is deemed valid on client side, it is then sent to the server by a websocket message.
     * The server will check it again and, if it's still valid, call applyOrder().
     */
    checkOrder : function (order) {
        /// <param name="order" type="Order"/>
        let heroToCheck = null;
        this.heros.forEach(function (hero) {
            if (order.source.guid === hero.guid) {
                heroToCheck = hero;
            }
        }, this);

        if (this.state === murmures.C.STATE_ENGINE_DEATH) return { valid : false, reason : 'You are dead!' };
        if (order.source === null) return { valid: false, reason: 'Order source is not defined' };
        else if (order.target === null) return { valid: false, reason: 'Order target is not defined' };
        else if (order.command === null) return { valid: false, reason: 'Order command is not defined' };
        else if (order.command !== 'move' && order.command !== 'attack') return { valid: false, reason: 'Order contains an unknown command' };
        //else if ((order.source.guid !== this.heros[0].guid)) return { valid: false, reason: 'You can only give orders to your own hero' };
        else if (typeof heroToCheck === 'undefined' || heroToCheck === null) return { valid: false, reason : 'order sent for an invalid hero' };
        else if (order.target.isWall()) return { valid: false, reason: 'You cannot target a wall' };

        else if (order.command === 'attack' && Math.abs(order.target.x - heroToCheck.position.x) > heroToCheck.range) return { valid: false, reason: 'Target is too far. Your attack range is: ' + heroToCheck.range };
        else if (order.command === 'attack' && Math.abs(order.target.y - heroToCheck.position.y) > heroToCheck.range) return { valid: false, reason: 'Target is too far. Your attack range is: ' + heroToCheck.range };
        else if (order.command === 'attack' && (!order.target.hasMob.code)) return { valid: false, reason: 'You cannot attack an empty tile' };
        else if (order.command === 'attack' && (order.target.hasMob.code) && (!order.target.hasMob.mob.onVisionCharacters[order.source.guid])) return { valid: false, reason: 'You cannot attack over an obstacle' };
        else if (order.command === 'attack') return { valid: true };

        else if (order.command === 'move' && Math.abs(order.target.x - heroToCheck.position.x) > 1) return { valid: false, reason: 'Target is too far. Your moving range is: 1' };
        else if (order.command === 'move' && Math.abs(order.target.y - heroToCheck.position.y) > 1) return { valid: false, reason: 'Target is too far. Your moving range is: 1' };
        else if (order.command === 'move' && (order.target.hasMob.code)) return { valid: false, reason: 'The target tile is occupied by a mob' };
        else return { valid: true };
    },

    saveOrder : function (order) {
        // This function is only called on server side
        let nbOrderDone = 0;
        for (let itHero = 0; itHero < this.heros.length ; itHero++) {
            if (this.heros[itHero].guid === order.source.guid) {
                this.heros[itHero].stateOrder = murmures.C.STATE_HERO_ORDER_GIVEN;
                if (typeof this.orderQueue === 'undefined') this.orderQueue = [];

                this.orderQueue.push(order);
                murmures.serverLog('Order saved');
            }

            if (this.heros[itHero].stateOrder === murmures.C.STATE_HERO_ORDER_GIVEN) {
                nbOrderDone += 1;
            }
        }
        this.reportQueue = [];
        if (this.heros.length !== nbOrderDone) {
            for (let itHero = 0; itHero < this.heros.length ; itHero++) {
                if (this.heros[itHero].stateOrder !== murmures.C.STATE_HERO_ORDER_GIVEN) {
                    this.heros[itHero].stateOrder = murmures.C.STATE_HERO_ORDER_INPROGRESS;
                    murmures.serverLog('Waiting for next order from following hero');
                    break;
                }
            }
        } else {
            murmures.serverLog('Apply all orders');
            for (let itOrders = 0; itOrders < this.orderQueue.length ; itOrders++) {
                this.applyOrder(this.orderQueue[itOrders]);
            }
            this.orderQueue = [];
            this.applyAI();
            murmures.serverLog('AI done');

            for (let itHero = 0; itHero < this.heros.length ; itHero++) {
                if (itHero === 0) {
                    this.heros[itHero].stateOrder = murmures.C.STATE_HERO_ORDER_INPROGRESS;
                } else {
                    this.heros[itHero].stateOrder = murmures.C.STATE_HERO_WAITING_FOR_ORDER;
                }
            }
        }
    },

    applyOrder : function (order) {
        // This function is only called on server side
        if (order.command === 'move') {
            if (typeof order.target.behavior !== 'undefined' && typeof order.target.behavior.move !== 'undefined') {
                murmures.Behavior[order.target.behavior.move.callback](order.source, order.target, order.target.behavior.move.params);
            }
            else {
                let tr1 = new murmures.TurnReport();
                tr1.build({
                    effect: 'characterMove',
                    character: order.source,
                    sourceTile: order.source.position.coordinates,
                    targetTile: order.target.coordinates,
                    priority: 10
                });
                this.reportQueue.push(tr1);
                order.source.move(order.target.x, order.target.y);
            }
        }
        else {
            this.level.mobs.forEach(function (mob) {
                if (mob.onVisionCharacters[this.guid] && mob.position.x === order.target.x && mob.position.y === order.target.y) {
                    let tr1 = new murmures.TurnReport();
                    tr1.build({
                        effect: 'projectileMove',
                        sourceTile: order.source.position.coordinates,
                        targetTile: order.target.coordinates,
                        priority: 20
                    });
                    this.reportQueue.push(tr1);
                    let tr2 = new murmures.TurnReport();
                    tr2.build({
                        effect: 'damage',
                        character: mob,
                        value: order.source.defaultDamageValue,
                        priority: 30
                    });
                    this.reportQueue.push(tr2);
                    mob.hitPoints -= order.source.defaultDamageValue;
                    if (mob.hitPoints <= 0) {
                        mob.hitPoints = 0;
                        mob.position.groundDeco = '_b1_02_blood_red00';
                    }
                }
            }, this);
        }
        murmures.serverLog('Moves / attacks done');
        for (let itMob=0; itMob < this.level.mobs.length; itMob++) {
            for(let itHero=0;itHero < this.heros.length;itHero++){
                this.level.mobs[itMob].onVisionCharacters[this.heros[itHero].guid] = false;
            }

        }
        let tilesProcessed = [];
        for (let itHero = 0; itHero < this.heros.length ; itHero++) {
            if (typeof tilesProcessed === 'undefined') {
                tilesProcessed = [];
            }
            tilesProcessed = this.heros[itHero].setVision(tilesProcessed);
        }
        murmures.serverLog('Vision done');
    },

    applyAI : function () {
        let heros = this.heros;
        let level = this.level;
        let bodies = this.bodies;
        this.level.mobs.forEach(function (mob) {
            if (mob.charSpotted) {
                let fireOnHero = false;
              /*
                for(let itVi=0;itVi < heros.length;itVi++){
                  murmures.serverLog(mob.onVisionCharacters[heros[itVi].guid] + "//" + heros[itVi].guid);
                }
              */
                //if (mob.onVision) {

                    for (let itHero = 0; itHero < heros.length; itHero++) {
                        if(mob.onVisionCharacters[heros[itHero].guid]){
                          if (Math.abs(mob.position.x - heros[itHero].position.x) <= mob.range && Math.abs(mob.position.y - heros[itHero].position.y) <= mob.range && mob.hitPoints > 0) {
                              let tr1 = new murmures.TurnReport();
                              tr1.build({
                                  effect: 'projectileMove',
                                  sourceTile: mob.position.coordinates,
                                  targetTile: heros[itHero].position.coordinates,
                                  priority: 120
                              });
                              this.reportQueue.push(tr1);
                              let tr2 = new murmures.TurnReport();
                              tr2.build({
                                  effect: 'damage',
                                  character: heros[itHero],
                                  value: mob.defaultDamageValue,
                                  priority: 130
                              });
                              this.reportQueue.push(tr2);
                              heros[itHero].hitPoints -= mob.defaultDamageValue;
                              fireOnHero = true;
                              if (heros[itHero].hitPoints <= 0) {
                                  heros[itHero].hitPoints = 0;
                                  this.state = murmures.C.STATE_ENGINE_DEATH;
                              }
                              break;
                          }
                      }
                    }
                //}
                if (!fireOnHero) {
                // TODO : move to hero
                }
            }
        }, this);
    }
};
<commit_msg>fix bug on attack mobs<commit_after>'use strict';
//debugger;

/**
 * @file GameEngine class. Part of the MurmuresJS project.
 * @author github.com/azziliz
 * @author github.com/thyshimrod
 * @license MIT
 */

/**
 * GameEngine is the main manager for all game objects.
 *
 * A single instance of this class is created when Node starts and is kept alive at all time afterwards.
 * This instance is in the global scope and can be accessed from any other class.
 * This is the only variable of the murmures project in the global scope.
 * During startup, the server loads all references data -stored in JSON files- into the engine instance.
 * This includes assets (list of all physical bodies and character templates), locale files, heroes and static levels.
 *
 * This class is also in charge of applying client orders to the game and calling AI methods.
 *
 * @class
 */
murmures.GameEngine = function () {
    /* No guid on GameEngine ; we just want one */
    /** @type {number} */
    this.tileSize = 32 | 0;
    /** @type {Object.<string, murmures.PhysicalBody>} */
    this.bodies = {};
    /** @type {Object.<string, Object.<string, string>>} */
    this.locale = {};
    /** @type {murmures.Level} */
    this.level = {};
    /** @type {Array.<murmures.Character>} */
    this.heros = [];
    /** @type {Array.<murmures.TurnReport>} */
    this.reportQueue = [];

    /* Server-only */
    /** @type {Array.<murmures.Level>} */
    this.levels = {};
    /** @type {Array.<string>} */
    this.levelIds = {};
    /** @type {number} */
    this.activeLevel = 0 | 0;
    /** @type {number} */
    this.gameTurn = 0 | 0;
    /** @type {number} */
    this.state = murmures.C.STATE_ENGINE_INIT | 0;
    /** @type {Array.<murmures.Order>} */
    this.orderQueue = [];
};

murmures.GameEngine.prototype = {

    /*
     * No build method here because initialization involves several Node-only functions.
     * We don't want to expose these functions to the client because they don't exist there.
     * Instead, we build the gameEngine master instance in server.js
     */

    /**
     * Synchronization method called on client side only.
     * Creates a full GameEngine objects from a JSON sent by the server.
     * Sub-classes are also synchronized recursively.
     * This function expects a full GameEngine object as input and is intended to overwrite the client instance completely.
     *
     * @param {Object} src - A parsed version of the stringified remote gameEngine instance.
     */
    initialize : function (src) {
        this.tileSize = src.tileSize;
        this.bodies = src.bodies;
        this.locale = src.locale;
        this.levelIds = src.levelIds;
        this.level = new murmures.Level();
        this.level.initialize(src.level);
        this.heros = [];
        src.heros.forEach(function (hero) {
            let tempHero = new murmures.Character();
            tempHero.initialize(hero);
            this.heros.push(tempHero);
        }, this);
        this.reportQueue = [];
        if (typeof src.reportQueue !== 'undefined') {
            src.reportQueue.forEach(function (report) {
                let tempReport = new murmures.TurnReport();
                tempReport.initialize(report);
                this.reportQueue.push(tempReport);
            }, this);
        }
        this.state = src.state;
    },

    /**
     * Synchronization method called on client side only.
     * This function receives a partial GameEngine as input and merges it into the client instance.
     */
    synchronize: function (src) {
        if (typeof src === 'undefined') return;
        let isNewLevel = (typeof src.level !== 'undefined') && (typeof src.level.guid !== 'undefined') && (this.level.guid !== src.level.guid);
        if (isNewLevel) {
            this.level = new murmures.Level();
            this.level.initialize(src.level);
        } else {
            this.level.synchronize(src.level);
        }
        if (src.state !== 'undefined') {
            this.state = src.state;
        }
        if (typeof src.heros !== 'undefined') {
            src.heros.forEach(function (remoteHero) {
                this.heros.forEach(function (localHero) {
                    if (localHero.guid === remoteHero.guid) {
                        localHero.synchronize(remoteHero);
                    }
                }, this);
            }, this);
        }
        this.reportQueue = [];
        if (typeof src.reportQueue !== 'undefined') {
            src.reportQueue.forEach(function (report) {
                let tempReport = new murmures.TurnReport();
                tempReport.initialize(report);
                this.reportQueue.push(tempReport);
            }, this);
        }
    },

    clone : function (src) {
        let tempHeros = [];
        for (let itHero = 0; itHero < this.heros.length ; itHero++) {
            let hero = this.heros[itHero].clone();
            tempHeros.push(hero);
        }
        return {
            state: this.state,
            level: this.level.clone(),
            heros: tempHeros
        };
    },

    compare : function (beforeState) {
        let ret = {};
        if (this.state != beforeState.state) {
            ret.state = this.state;
        }
        let level_ = this.level.compare(beforeState.level);

        if (typeof level_ !== 'undefined') ret.level = level_;
        let heros_ = [];
        for (let itHero = 0; itHero < this.heros.length; itHero++) {
            for (let itHero_ = 0; itHero_ < beforeState.heros.length; itHero_++) {
                if (beforeState.heros[itHero_].guid === this.heros[itHero].guid) {
                    let hero_ = this.heros[itHero].compare(beforeState.heros[itHero_]);
                    if (typeof hero_ !== 'undefined') heros_.push(hero_);
                }
            }
        }
        if (heros_.length > 0) {
            ret.heros = heros_;
        }
        if (this.reportQueue.length > 0) {
            ret.reportQueue = this.reportQueue;
        }
        for (let prop in ret) {
            // only returns ret if not empty
            return ret;
        }
        // otherwise, no return = undefined
    },

    getHeroByGuid: function (guid) {
        this.heros.forEach(function (hero) {
            if (hero.guid === guid) return hero;
        }, this);
    },

    /**
     * This function is called on client and server side.
     * If the order is deemed valid on client side, it is then sent to the server by a websocket message.
     * The server will check it again and, if it's still valid, call applyOrder().
     */
    checkOrder : function (order) {
        /// <param name="order" type="Order"/>
        let heroToCheck = null;
        this.heros.forEach(function (hero) {
            if (order.source.guid === hero.guid) {
                heroToCheck = hero;
            }
        }, this);

        if (this.state === murmures.C.STATE_ENGINE_DEATH) return { valid : false, reason : 'You are dead!' };
        if (order.source === null) return { valid: false, reason: 'Order source is not defined' };
        else if (order.target === null) return { valid: false, reason: 'Order target is not defined' };
        else if (order.command === null) return { valid: false, reason: 'Order command is not defined' };
        else if (order.command !== 'move' && order.command !== 'attack') return { valid: false, reason: 'Order contains an unknown command' };
        //else if ((order.source.guid !== this.heros[0].guid)) return { valid: false, reason: 'You can only give orders to your own hero' };
        else if (typeof heroToCheck === 'undefined' || heroToCheck === null) return { valid: false, reason : 'order sent for an invalid hero' };
        else if (order.target.isWall()) return { valid: false, reason: 'You cannot target a wall' };

        else if (order.command === 'attack' && Math.abs(order.target.x - heroToCheck.position.x) > heroToCheck.range) return { valid: false, reason: 'Target is too far. Your attack range is: ' + heroToCheck.range };
        else if (order.command === 'attack' && Math.abs(order.target.y - heroToCheck.position.y) > heroToCheck.range) return { valid: false, reason: 'Target is too far. Your attack range is: ' + heroToCheck.range };
        else if (order.command === 'attack' && (!order.target.hasMob.code)) return { valid: false, reason: 'You cannot attack an empty tile' };
        else if (order.command === 'attack' && (order.target.hasMob.code) && (!order.target.hasMob.mob.onVisionCharacters[order.source.guid])) return { valid: false, reason: 'You cannot attack over an obstacle' };
        else if (order.command === 'attack') return { valid: true };

        else if (order.command === 'move' && Math.abs(order.target.x - heroToCheck.position.x) > 1) return { valid: false, reason: 'Target is too far. Your moving range is: 1' };
        else if (order.command === 'move' && Math.abs(order.target.y - heroToCheck.position.y) > 1) return { valid: false, reason: 'Target is too far. Your moving range is: 1' };
        else if (order.command === 'move' && (order.target.hasMob.code)) return { valid: false, reason: 'The target tile is occupied by a mob' };
        else return { valid: true };
    },

    saveOrder : function (order) {
        // This function is only called on server side
        let nbOrderDone = 0;
        for (let itHero = 0; itHero < this.heros.length ; itHero++) {
            if (this.heros[itHero].guid === order.source.guid) {
                this.heros[itHero].stateOrder = murmures.C.STATE_HERO_ORDER_GIVEN;
                if (typeof this.orderQueue === 'undefined') this.orderQueue = [];

                this.orderQueue.push(order);
                murmures.serverLog('Order saved');
            }

            if (this.heros[itHero].stateOrder === murmures.C.STATE_HERO_ORDER_GIVEN) {
                nbOrderDone += 1;
            }
        }
        this.reportQueue = [];
        if (this.heros.length !== nbOrderDone) {
            for (let itHero = 0; itHero < this.heros.length ; itHero++) {
                if (this.heros[itHero].stateOrder !== murmures.C.STATE_HERO_ORDER_GIVEN) {
                    this.heros[itHero].stateOrder = murmures.C.STATE_HERO_ORDER_INPROGRESS;
                    murmures.serverLog('Waiting for next order from following hero');
                    break;
                }
            }
        } else {
            murmures.serverLog('Apply all orders');
            for (let itOrders = 0; itOrders < this.orderQueue.length ; itOrders++) {
                this.applyOrder(this.orderQueue[itOrders]);
            }
            this.orderQueue = [];
            this.applyAI();
            murmures.serverLog('AI done');

            for (let itHero = 0; itHero < this.heros.length ; itHero++) {
                if (itHero === 0) {
                    this.heros[itHero].stateOrder = murmures.C.STATE_HERO_ORDER_INPROGRESS;
                } else {
                    this.heros[itHero].stateOrder = murmures.C.STATE_HERO_WAITING_FOR_ORDER;
                }
            }
        }
    },

    applyOrder : function (order) {
        // This function is only called on server side
        if (order.command === 'move') {
            if (typeof order.target.behavior !== 'undefined' && typeof order.target.behavior.move !== 'undefined') {
                murmures.Behavior[order.target.behavior.move.callback](order.source, order.target, order.target.behavior.move.params);
            }
            else {
                let tr1 = new murmures.TurnReport();
                tr1.build({
                    effect: 'characterMove',
                    character: order.source,
                    sourceTile: order.source.position.coordinates,
                    targetTile: order.target.coordinates,
                    priority: 10
                });
                this.reportQueue.push(tr1);
                order.source.move(order.target.x, order.target.y);
            }
        }
        else {
            this.level.mobs.forEach(function (mob) {
                if (mob.onVisionCharacters[order.source.guid] && mob.position.x === order.target.x && mob.position.y === order.target.y) {
                    let tr1 = new murmures.TurnReport();
                    tr1.build({
                        effect: 'projectileMove',
                        sourceTile: order.source.position.coordinates,
                        targetTile: order.target.coordinates,
                        priority: 20
                    });
                    this.reportQueue.push(tr1);
                    let tr2 = new murmures.TurnReport();
                    tr2.build({
                        effect: 'damage',
                        character: mob,
                        value: order.source.defaultDamageValue,
                        priority: 30
                    });
                    this.reportQueue.push(tr2);
                    mob.hitPoints -= order.source.defaultDamageValue;
                    if (mob.hitPoints <= 0) {
                        mob.hitPoints = 0;
                        mob.position.groundDeco = '_b1_02_blood_red00';
                    }
                }
            }, this);
        }
        murmures.serverLog('Moves / attacks done');
        for (let itMob=0; itMob < this.level.mobs.length; itMob++) {
            for(let itHero=0;itHero < this.heros.length;itHero++){
                this.level.mobs[itMob].onVisionCharacters[this.heros[itHero].guid] = false;
            }

        }
        let tilesProcessed = [];
        for (let itHero = 0; itHero < this.heros.length ; itHero++) {
            if (typeof tilesProcessed === 'undefined') {
                tilesProcessed = [];
            }
            tilesProcessed = this.heros[itHero].setVision(tilesProcessed);
        }
        murmures.serverLog('Vision done');
    },

    applyAI : function () {
        let heros = this.heros;
        let level = this.level;
        let bodies = this.bodies;
        this.level.mobs.forEach(function (mob) {
            if (mob.charSpotted) {
                let fireOnHero = false;
              /*
                for(let itVi=0;itVi < heros.length;itVi++){
                  murmures.serverLog(mob.onVisionCharacters[heros[itVi].guid] + "//" + heros[itVi].guid);
                }
              */
                //if (mob.onVision) {

                    for (let itHero = 0; itHero < heros.length; itHero++) {
                        if(mob.onVisionCharacters[heros[itHero].guid]){
                          if (Math.abs(mob.position.x - heros[itHero].position.x) <= mob.range && Math.abs(mob.position.y - heros[itHero].position.y) <= mob.range && mob.hitPoints > 0) {
                              let tr1 = new murmures.TurnReport();
                              tr1.build({
                                  effect: 'projectileMove',
                                  sourceTile: mob.position.coordinates,
                                  targetTile: heros[itHero].position.coordinates,
                                  priority: 120
                              });
                              this.reportQueue.push(tr1);
                              let tr2 = new murmures.TurnReport();
                              tr2.build({
                                  effect: 'damage',
                                  character: heros[itHero],
                                  value: mob.defaultDamageValue,
                                  priority: 130
                              });
                              this.reportQueue.push(tr2);
                              heros[itHero].hitPoints -= mob.defaultDamageValue;
                              fireOnHero = true;
                              if (heros[itHero].hitPoints <= 0) {
                                  heros[itHero].hitPoints = 0;
                                  this.state = murmures.C.STATE_ENGINE_DEATH;
                              }
                              break;
                          }
                      }
                    }
                //}
                if (!fireOnHero) {
                // TODO : move to hero
                }
            }
        }, this);
    }
};
<|endoftext|>


<commit_before>sort_applet_selection_panel = function () {
	var qty = $("dashboard_applets_new").childNodes.length;
	var last = null;
	for (i=0; i<qty; i++) {
		if (!$("dashboard_applets_new").childNodes[i].getAttribute("order")) continue;
		var last_node = $("dashboard_applets_new").childNodes[last];
		var cur_node = $("dashboard_applets_new").childNodes[i];
		if (last) {
			if (parseInt(last_node.getAttribute("order")) > parseInt(cur_node.getAttribute("order"))) {
				for (j=0; j<i; j++) {
					var candicate_node = $("dashboard_applets_new").childNodes[j];
					if (parseInt(candicate_node.getAttribute("order")) > parseInt(cur_node.getAttribute("order"))) {
						$("dashboard_applets_new").insertBefore(cur_node, candicate_node);
						break;
					}
				}
			}
		}
		last = i;
	}
}

dashboard_activate = function(tab, default_dash) {
	if(!$('dashboard')) return;
	for(var id=0; id<3; id++)
		Sortable.create("dashboard_applets_"+tab+"_"+id,{dropOnEmpty:true,tag:'div',containment:["dashboard_applets_new","dashboard_applets_"+tab+"_0","dashboard_applets_"+tab+"_1","dashboard_applets_"+tab+"_2"],constraint:false, handle: 'handle',ghosting: false,onUpdate:function(c){
			// find the recently added applet
			for (w in c.childNodes)
				if (c.childNodes[w] && c.childNodes[w].id && c.childNodes[w].id.indexOf("ab_item_new_")!=-1) {
					var appletCopy = c.childNodes[w].cloneNode(true);
					$("dashboard_applets_new").appendChild(appletCopy);
					c.childNodes[w].id = "copy_"+c.childNodes[w].id;
					var links = c.childNodes[w].getElementsByTagName('A');
					for (i=0;i<links.length;i++) if (links[i].id.substr(0,24)=="dashboard_remove_applet_") {
						links[i].setAttribute("id","copy_"+links[i].id);
					}
					var divs = c.childNodes[w].getElementsByTagName('DIV');
					for (i=0;i<divs.length;i++) if (divs[i].id.substr(0,25)=="dashboard_applet_content_") {
						divs[i].setAttribute("id","copy_"+divs[i].id);
					}
					appletCopy.style.opacity = 1;
					appletCopy.style.top = 0;
					appletCopy.style.left = 0;
					sort_applet_selection_panel();
				}
			new Ajax.Request("modules/Base/Dashboard/update.php",{
				method: "post",
				parameters: {
					data: Sortable.serialize(c.id),
					default_dash: default_dash,
					tab: tab,
					call: 'base'
				},
				onSuccess:function(t) {
					eval(t.responseText);
				}
			});
		}});

	if ($("dashboard_applets_new"))
		Sortable.create("dashboard_applets_new",{dropOnEmpty:true,tag:'div',containment:["dashboard_applets_new","dashboard_applets_"+tab+"_0","dashboard_applets_"+tab+"_1","dashboard_applets_"+tab+"_2"],constraint:false, handle: 'handle',ghosting: false, onUpdate:function(c){
			// remove instanced applets from the list
			for (w in c.childNodes)
				if (c.childNodes[w] && c.childNodes[w].id && c.childNodes[w].id.indexOf("ab_item_new_")==-1)
					c.childNodes[w].style.display="none";
			sort_applet_selection_panel();
			new Ajax.Request("modules/Base/Dashboard/update.php",{
				method: "post",
				parameters: {
					data: Sortable.serialize(c.id),
					default_dash: default_dash,
					tab: tab,
					call: 'new'
				},
				onSuccess:function(t) {
					eval(t.responseText);
				}
			});
		}});

	var applets = $A(document.getElementsByClassName('applet', 'dashboard'));
	applets.each(function (appl) {
			var content = appl.getElementsByClassName('content')[0]
			var toggle = appl.getElementsByClassName('toggle')[0];
			if(toggle)
				Event.observe(toggle, 'click', function (e) { Effect.toggle(content, 'slide', {duration:0.3}); },false);
	});
}

remove_applet = function(id, default_dash) {
	if ($("ab_item_"+id))
		Effect.Fade("ab_item_"+id);
	new Ajax.Request("modules/Base/Dashboard/remove_applet.php",{
		method: "post",
		parameters: {
			id: id,
			default_dash: default_dash
		},
		onSuccess:function(t) {
			eval(t.responseText);
		}
	});
}

get_new_dashboard_tab_name = function(query, error, id) {
	var name = prompt(query);
	if (name == null) return false;
	if (name == '') {
		alert(error);
		return false;
	}
	$('dashboard_tab_name').value = name;
	$('dashboard_tab_id').value = id;
	return true;
}

dashboard_prepare_filter_box = function(focus, message) {
	if (focus && $("dashboard_applets_filter").value == message) {
		$("dashboard_applets_filter").style.color = "";
		$("dashboard_applets_filter").value = "";
	}
	if (!focus && $("dashboard_applets_filter").value == "") {
		$("dashboard_applets_filter").style.color = "#555";
		$("dashboard_applets_filter").value = message;
	}
}

dashboard_filter_applets = function() {
	var str = $("dashboard_applets_filter").value.toLowerCase();
	var nodes = $("dashboard_applets_new").childNodes;
	var qty = nodes.length;
	for (i=0; i<qty; i++) {
		if (nodes[i] && nodes[i].id && nodes[i].id.indexOf("ab_item_new_")!=-1) {
			var searchkey = nodes[i].getAttribute('searchkey');
			$('module-indicator').innerHTML = searchkey;
			if (searchkey.toLowerCase().indexOf(str)!=-1) nodes[i].style.display="";
			else nodes[i].style.display="none";
		}
	}
}
<commit_msg>Dashboard - trivial bug fix<commit_after>sort_applet_selection_panel = function () {
	var qty = $("dashboard_applets_new").childNodes.length;
	var last = null;
	for (i=0; i<qty; i++) {
		if (!$("dashboard_applets_new").childNodes[i].getAttribute("order")) continue;
		var last_node = $("dashboard_applets_new").childNodes[last];
		var cur_node = $("dashboard_applets_new").childNodes[i];
		if (last) {
			if (parseInt(last_node.getAttribute("order")) > parseInt(cur_node.getAttribute("order"))) {
				for (j=0; j<i; j++) {
					var candicate_node = $("dashboard_applets_new").childNodes[j];
					if (parseInt(candicate_node.getAttribute("order")) > parseInt(cur_node.getAttribute("order"))) {
						$("dashboard_applets_new").insertBefore(cur_node, candicate_node);
						break;
					}
				}
			}
		}
		last = i;
	}
}

dashboard_activate = function(tab, default_dash) {
	if(!$('dashboard')) return;
	for(var id=0; id<3; id++)
		Sortable.create("dashboard_applets_"+tab+"_"+id,{dropOnEmpty:true,tag:'div',containment:["dashboard_applets_new","dashboard_applets_"+tab+"_0","dashboard_applets_"+tab+"_1","dashboard_applets_"+tab+"_2"],constraint:false, handle: 'handle',ghosting: false,onUpdate:function(c){
			// find the recently added applet
			for (w in c.childNodes)
				if (c.childNodes[w] && c.childNodes[w].id && c.childNodes[w].id.indexOf("ab_item_new_")!=-1) {
					var appletCopy = c.childNodes[w].cloneNode(true);
					$("dashboard_applets_new").appendChild(appletCopy);
					c.childNodes[w].id = "copy_"+c.childNodes[w].id;
					var links = c.childNodes[w].getElementsByTagName('A');
					for (i=0;i<links.length;i++) if (links[i].id.substr(0,24)=="dashboard_remove_applet_") {
						links[i].setAttribute("id","copy_"+links[i].id);
					}
					var divs = c.childNodes[w].getElementsByTagName('DIV');
					for (i=0;i<divs.length;i++) if (divs[i].id.substr(0,25)=="dashboard_applet_content_") {
						divs[i].setAttribute("id","copy_"+divs[i].id);
					}
					appletCopy.style.opacity = 1;
					appletCopy.style.top = 0;
					appletCopy.style.left = 0;
					sort_applet_selection_panel();
				}
			new Ajax.Request("modules/Base/Dashboard/update.php",{
				method: "post",
				parameters: {
					data: Sortable.serialize(c.id),
					default_dash: default_dash,
					tab: tab,
					call: 'base'
				},
				onSuccess:function(t) {
					eval(t.responseText);
				}
			});
		}});

	if ($("dashboard_applets_new"))
		Sortable.create("dashboard_applets_new",{dropOnEmpty:true,tag:'div',containment:["dashboard_applets_new","dashboard_applets_"+tab+"_0","dashboard_applets_"+tab+"_1","dashboard_applets_"+tab+"_2"],constraint:false, handle: 'handle',ghosting: false, onUpdate:function(c){
			// remove instanced applets from the list
			for (w in c.childNodes)
				if (c.childNodes[w] && c.childNodes[w].id && c.childNodes[w].id.indexOf("ab_item_new_")==-1)
					c.childNodes[w].style.display="none";
			sort_applet_selection_panel();
			new Ajax.Request("modules/Base/Dashboard/update.php",{
				method: "post",
				parameters: {
					data: Sortable.serialize(c.id),
					default_dash: default_dash,
					tab: tab,
					call: 'new'
				},
				onSuccess:function(t) {
					eval(t.responseText);
				}
			});
		}});

	var applets = $A(document.getElementsByClassName('applet', 'dashboard'));
	applets.each(function (appl) {
			var content = appl.getElementsByClassName('content')[0]
			var toggle = appl.getElementsByClassName('toggle')[0];
			if(toggle)
				Event.observe(toggle, 'click', function (e) { Effect.toggle(content, 'slide', {duration:0.3}); },false);
	});
}

remove_applet = function(id, default_dash) {
	if ($("ab_item_"+id))
		Effect.Fade("ab_item_"+id);
	new Ajax.Request("modules/Base/Dashboard/remove_applet.php",{
		method: "post",
		parameters: {
			id: id,
			default_dash: default_dash
		},
		onSuccess:function(t) {
			eval(t.responseText);
		}
	});
}

get_new_dashboard_tab_name = function(query, error, id) {
	var name = prompt(query);
	if (name == null) return false;
	if (name == '') {
		alert(error);
		return false;
	}
	$('dashboard_tab_name').value = name;
	$('dashboard_tab_id').value = id;
	return true;
}

dashboard_prepare_filter_box = function(focus, message) {
	if (focus && $("dashboard_applets_filter").value == message) {
		$("dashboard_applets_filter").style.color = "";
		$("dashboard_applets_filter").value = "";
	}
	if (!focus && $("dashboard_applets_filter").value == "") {
		$("dashboard_applets_filter").style.color = "#555";
		$("dashboard_applets_filter").value = message;
	}
}

dashboard_filter_applets = function() {
	var str = $("dashboard_applets_filter").value.toLowerCase();
	var nodes = $("dashboard_applets_new").childNodes;
	var qty = nodes.length;
	for (i=0; i<qty; i++) {
		if (nodes[i] && nodes[i].id && nodes[i].id.indexOf("ab_item_new_")!=-1) {
			var searchkey = nodes[i].getAttribute('searchkey');
			if (searchkey.toLowerCase().indexOf(str)!=-1) nodes[i].style.display="";
			else nodes[i].style.display="none";
		}
	}
}
<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';

var TwitterAPI = require('node-twitter-api'),
    fs = require('fs'),
    config = require('./config.json');

var twitter = new TwitterAPI({
    consumerKey: config.consumerKey,
    consumerSecret: config.consumerSecret
});

var accessToken = config.accessToken,
    accessTokenSecret = config.accessTokenSecret;

var uniqueFacts = {},
    consecutiveLackOfFacts = 0;

function finish() {
    fs.writeFile(
        'facts.json',
        JSON.stringify(Object.keys(uniqueFacts)),
        function (error) {
            if (error) {
                console.log('Saving failed! :(');
            }
        }
    );
}

function fetch(since_id) {
    console.log('Fetching facts.');
    twitter.getTimeline(
        'user_timeline',
        (function () {
            var params = {
                screen_name: 'CrockfordFacts',
                count: 200
            };
            if (since_id) {
                params.since_id = since_id;
            }
            return params;
        }()),
        accessToken,
        accessTokenSecret,
        function (error, data) {
            if (error) {
                console.log(error);
                console.log('Error. We are done.');
                return finish();
            }
            if (!data.length) {
                console.log('No more tweets. We are done.');
                return finish();
            }

            var follow, learned = 0;
            data.forEach(function (tweet) {
                if (!uniqueFacts.hasOwnProperty(tweet.text)) {
                    uniqueFacts[tweet.text] = null;
                    learned += 1;
                }
                follow = tweet.id_str;
            });

            console.log('Learned ' + learned + ' facts');
            if (!learned) {
                consecutiveLackOfFacts += 1;
            } else {
                consecutiveLackOfFacts = 0;
            }

            if (consecutiveLackOfFacts >= 3) {
                console.log('Three consecutive fetches, and no new facts. We are done.');
                return finish();
            }

            fetch(follow);
        }
    );
}

fetch();
<commit_msg>Pretty-print collector output<commit_after>/*jslint node: true */
'use strict';

var TwitterAPI = require('node-twitter-api'),
    fs = require('fs'),
    config = require('./config.json');

var twitter = new TwitterAPI({
    consumerKey: config.consumerKey,
    consumerSecret: config.consumerSecret
});

var accessToken = config.accessToken,
    accessTokenSecret = config.accessTokenSecret;

var uniqueFacts = {},
    consecutiveLackOfFacts = 0;

function finish() {
    fs.writeFile(
        'facts.json',
        JSON.stringify(Object.keys(uniqueFacts), null, 4),
        function (error) {
            if (error) {
                console.log('Saving failed! :(');
            }
        }
    );
}

function fetch(since_id) {
    console.log('Fetching facts.');
    twitter.getTimeline(
        'user_timeline',
        (function () {
            var params = {
                screen_name: 'CrockfordFacts',
                count: 200
            };
            if (since_id) {
                params.since_id = since_id;
            }
            return params;
        }()),
        accessToken,
        accessTokenSecret,
        function (error, data) {
            if (error) {
                console.log(error);
                console.log('Error. We are done.');
                return finish();
            }
            if (!data.length) {
                console.log('No more tweets. We are done.');
                return finish();
            }

            var follow, learned = 0;
            data.forEach(function (tweet) {
                if (!uniqueFacts.hasOwnProperty(tweet.text)) {
                    uniqueFacts[tweet.text] = null;
                    learned += 1;
                }
                follow = tweet.id_str;
            });

            console.log('Learned ' + learned + ' facts');
            if (!learned) {
                consecutiveLackOfFacts += 1;
            } else {
                consecutiveLackOfFacts = 0;
            }

            if (consecutiveLackOfFacts >= 3) {
                console.log('Three consecutive fetches, and no new facts. We are done.');
                return finish();
            }

            fetch(follow);
        }
    );
}

fetch();
<|endoftext|>


<commit_before>"use strict";

var events = require('events');
var EventEmitter = events.EventEmitter;

var Wrappers = require('./wrappers');

var exports = {};

// Only define Node-style usage using sync I/O if in node.
(function () {
    var dirname = require('path').dirname;
    var resolve = require('path').resolve;
    var existsSync = require('fs').existsSync || require('path').existsSync;
    var realpathSync = require('fs').realpathSync;
    var exists = require('fs').exists || require('path').exists;
    var realpath = require('fs').realpath;
    var packagePathCache = {};

    exports.loadConfig = loadConfig;
    exports.resolveConfig = resolveConfig;

    // This is assumed to be used at startup and uses sync I/O as well as can
    // throw exceptions.  It loads and parses a config file.
    function loadConfig(configPath, callback) {
      var config = require(configPath);
      var base = dirname(configPath);

      var ext_config = resolveDefaultWrappers(config, base, callback);
      return resolveConfig(ext_config, base, callback);
    }

    function resolveConfig(config, base, callback) {
        if (!callback)
            return resolveConfigSync(config, base);
        else
            resolveConfigAsync(config, base, callback);
    }

    function resolveConfigSync(config, base) {
        config.forEach(function (plugin, index) {
            // Shortcut where string is used for plugin without any options.
            if (typeof plugin === "string") {
                plugin = config[index] = { packagePath: plugin };
            }

            // The plugin is a package on the disk.  We need to load it.
            if (plugin.hasOwnProperty("packagePath")
                  && !(plugin.hasOwnProperty("setup") || plugin.hasOwnProperty("provides"))) {
                var defaults = resolveModuleSync(base, plugin.packagePath);
                Object.keys(defaults).forEach(function (key) {
                    if (!plugin.hasOwnProperty(key)) {
                        plugin[key] = defaults[key];
                    }
                });

                // if no, don't load setup function, etc and create hooks
                plugin.packagePath = defaults.packagePath; // now has the file to load

                // not all packages have a main entry point
                // in which case require fails.
                try{
                    plugin.setup = require(plugin.packagePath);
                } catch(e){}

                resolveWrappedServicesSync(base, plugin);
            }
        });
        return config;
    }

    function resolveConfigAsync(config, base, callback) {
        function resolveNext(i) {
            if (i >= config.length) {
                return callback(null, config);
            }

            var plugin = config[i];

            // Shortcut where string is used for plugin without any options.
            if (typeof plugin === "string") {
                plugin = config[i] = { packagePath: plugin };
            }

            // The plugin is a package on the disk.  We need to load it.
            if (plugin.hasOwnProperty("packagePath")
                  && !(plugin.hasOwnProperty("setup") || plugin.hasOwnProperty("provides"))) {
                resolveModuleAsync(base, plugin.packagePath, function(err, defaults) {
                    if (err) return callback(err);

                    Object.keys(defaults).forEach(function (key) {
                        if (!plugin.hasOwnProperty(key)) {
                            plugin[key] = defaults[key];
                        }
                    });
                    plugin.packagePath = defaults.packagePath;
                    try {
                        plugin.setup = require(plugin.packagePath);
                    } catch(e) {
                        return callback(e);
                    }

                    // if the plugin uses package wrappers
                    resolveWrappedServicesSync(base, plugin);

                    return resolveNext(++i);
                });
                return;
            }

            return resolveNext(++i);
        }

        resolveNext(0);
    }

    // Loads a module, getting metadata from either it's package.json or export
    // object.
    function resolveModuleSync(base, modulePath) {
        var packagePath;
        try {
            packagePath = resolvePackageSync(base, modulePath + "/package.json");
        }
        catch (err) {
            if (err.code !== "ENOENT") throw err;
        }
        var packageJson = packagePath && require(packagePath) || {};
        var metadata = packagePath && packageJson.plugin || {};
        if (packagePath) {
            modulePath = dirname(packagePath);
        } else {
            modulePath = resolvePackageSync(base, modulePath);
        }
        // pacakgeJson - may not have a main entry point
        if(packageJson.main){
            var module = require(modulePath);
            metadata.provides = metadata.provides || module.provides || [];
            metadata.consumes = metadata.consumes || module.consumes || [];
        }
        metadata.packagePath = modulePath;
        return metadata;
    }

    // Loads a module, getting metadata from either it's package.json or export
    // object.
    function resolveModuleAsync(base, modulePath, callback) {
        resolvePackageAsync(base, modulePath + "/package.json", function(err, packagePath) {
            //if (err && err.code !== "ENOENT") return callback(err);

            var metadata = {};
            if (!err) {
                try {
                    metadata = packagePath && require(packagePath).plugin || {};
                } catch(e) {
                    return callback(e);
                }
            }

            (function(next) {
                if (err) {
                    //@todo Fabian what is a better way?
                    resolvePackageAsync(base, modulePath + ".js", next);
                }
                else if (packagePath) {
                    next(null, dirname(packagePath));
                }
                else {
                    resolvePackageAsync(base, modulePath, next);
                }
            })(function(err, modulePath) {
                if (err) return callback(err);

                var module;
                try {
                    module = require(modulePath);
                } catch(e) {
                    return callback(e);
                }

                metadata.provides = metadata.provides || module.provides || [];
                metadata.consumes = metadata.consumes || module.consumes || [];
                metadata.packagePath = modulePath;
                callback(null, metadata);
            });
        });
    }

    // Node style package resolving so that plugins' package.json can be found relative to the config file
    // It's not the full node require system algorithm, but it's the 99% case
    // This throws, make sure to wrap in try..catch
    function resolvePackageSync(base, packagePath) {
        var originalBase = base;
        if (!(base in packagePathCache)) {
            packagePathCache[base] = {};
        }
        var cache = packagePathCache[base];
        if (packagePath in cache) {
            return cache[packagePath];
        }
        var newPath, newBase;
        if (packagePath[0] === "." || packagePath[0] === "/") {
            newPath = resolve(base, packagePath);
            if (existsSync(newPath)) {
                newPath = realpathSync(newPath);
                cache[packagePath] = newPath;
                return newPath;
            }
        }
        else {
            while (base) {
                newPath = resolve(base, "node_modules", packagePath);
                if (existsSync(newPath)) {
                    newPath = realpathSync(newPath);
                    cache[packagePath] = newPath;
                    return newPath;
                }
                newBase = resolve(base, '..');
                if (base === newBase) {
                    break;
                }
                base = newBase;
            }
        }
        var err = new Error("Can't find '" + packagePath + "' relative to '" + originalBase + "'");
        err.code = "ENOENT";
        throw err;
    }

    function resolvePackageAsync(base, packagePath, callback) {
        var originalBase = base;
        if (!packagePathCache.hasOwnProperty(base)) {
            packagePathCache[base] = {};
        }
        var cache = packagePathCache[base];
        if (cache.hasOwnProperty(packagePath)) {
            return callback(null, cache[packagePath]);
        }
        if (packagePath[0] === "." || packagePath[0] === "/") {
            var newPath = resolve(base, packagePath);
            exists(newPath, function(exists) {
                if (exists) {
                    realpath(newPath, function(err, newPath) {
                        if (err) return callback(err);

                        cache[packagePath] = newPath;
                        return callback(null, newPath);
                    });
                } else {
                    var err = new Error("Can't find '" + packagePath + "' relative to '" + originalBase + "'");
                    err.code = "ENOENT";
                    return callback(err);
                }
            });
        }
        else {
            tryNext(base);
        }

        function tryNext(base) {
            if (base == "/") {
                var err = new Error("Can't find '" + packagePath + "' relative to '" + originalBase + "'");
                err.code = "ENOENT";
                return callback(err);
            }

            var newPath = resolve(base, "node_modules", packagePath);
            exists(newPath, function(exists) {
                if (exists) {
                    realpath(newPath, function(err, newPath) {
                        if (err) return callback(err);

                        cache[packagePath] = newPath;
                        return callback(null, newPath);
                    });
                } else {
                    var nextBase = resolve(base, '..');
                    if (nextBase === base)
                        tryNext(null);
                    else
                        tryNext(nextBase);
                }
            });
        }
    }

    // Load all objects that are provided by the module (they will be consumed by wrapper)
    function resolveWrappedServicesSync(base, plugin){
        if(!plugin.packageWrapper){
            plugin.packageWrapper = Wrappers.defaultWrapperName;
        }
        var wrapperInst = Wrappers.newWrapper(plugin.packageWrapper);
        wrapperInst.resolveConfig(plugin, base);
        plugin.packageWrapper = wrapperInst;
    }
}());

exports.createApp = createApp;
exports.Archie = Archie;

// Check a plugin config list for bad dependencies and throw on error
function checkConfig(config) {

    // Check for the required fields in each plugin.
    config.forEach(function (plugin) {
        if (plugin.checked) { return; }
        // legacy: not all packages have setup functions
        //if (!plugin.hasOwnProperty("setup")) {
        //    throw new Error("Plugin is missing the setup function " + JSON.stringify(plugin));
        //}
        if (!plugin.hasOwnProperty("provides")) {
            throw new Error("Plugin is missing the provides array " + JSON.stringify(plugin));
        }
        if (!plugin.hasOwnProperty("consumes")) {
            throw new Error("Plugin is missing the consumes array " + JSON.stringify(plugin));
        }
    });

    return checkCycles(config);
}

function checkCycles(config) {
    var plugins = [];
    config.forEach(function(pluginConfig, index) {
        plugins.push({
            packagePath: pluginConfig.packagePath,
            provides: pluginConfig.provides.concat(),
            consumes: pluginConfig.consumes.concat(),
            i: index
        });
    });

    var resolved = {
        hub: true
    };
    var changed = true;
    var sorted = [];

    while(plugins.length && changed) {
        changed = false;

        plugins.concat().forEach(function(plugin) {
            var consumes = plugin.consumes.concat();

            var resolvedAll = true;
            for (var i=0; i<consumes.length; i++) {
                var service = consumes[i];
                if (!resolved[service]) {
                    resolvedAll = false;
                } else {
                    plugin.consumes.splice(plugin.consumes.indexOf(service), 1);
                }
            }

            if (!resolvedAll)
                return;

            plugins.splice(plugins.indexOf(plugin), 1);
            plugin.provides.forEach(function(service) {
                resolved[service] = true;
            });
            sorted.push(config[plugin.i]);
            changed = true;
        });
    }

    if (plugins.length) {
        var unresolved = {};
        plugins.forEach(function(plugin) {
            delete plugin.config;
            plugin.consumes.forEach(function(name) {
                if (unresolved[name] == false)
                    return;
                if (!unresolved[name])
                    unresolved[name] = [];
                unresolved[name].push(plugin.packagePath);
            });
            plugin.provides.forEach(function(name) {
                unresolved[name] = false;
            });
        });

        Object.keys(unresolved).forEach(function(name) {
            if (unresolved[name] == false)
                delete unresolved[name];
        });

        console.error("Could not resolve dependencies of these plugins:", plugins);
        console.error("Resolved services:", Object.keys(resolved));
        console.error("Missing services:", unresolved);
        throw new Error("Could not resolve dependencies");
    }

    return sorted;
}

function Archie(config) {
    var app = this;
    app.config = [];
    app.destructors = [];
    app.services = {
        hub: {
            on: function (name, callback) {
                app.on(name, callback);
            }
        }
    };

    // Give createApp some time to subscribe to our "ready" event
    (typeof process === "object" ? process.nextTick : setTimeout)(function() {
        app.loadPlugins(config, function(err) {
            if (err) {
                throw err;
            }
            app.emit("ready", app);
        });
    });
}

Archie.prototype = Object.create(EventEmitter.prototype, {constructor:{value:Archie}});

Archie.prototype.destroy = function() {
    var app = this;

    app.destructors.forEach(function(destroy) {
        destroy();
    });

    app.destructors = [];
};

Archie.prototype.loadPlugins = function(config, callback) {
    var app = this;

    var sortedConfig;
    try {
        sortedConfig = checkConfig(config.concat(app.config));
    }
    catch(ex) {
        return callback(ex);
    }

    // prevent double loading of plugins
    sortedConfig = sortedConfig.filter(function(c) {
        return config.indexOf(c) > -1;
    });

    var p;
    function next(err) {
        if (err) {
            return callback(err);
        }
        if (p && app.config.indexOf(p) === -1) {
            app.config.push(p);
        }

        p = sortedConfig.shift();
        if (!p) {
            return callback();
        }
        app.registerPlugin(p, next);
    }
    next();
};

/**
 * Register a plugin in the service
 */
Archie.prototype.registerPlugin = function(plugin, next) {
    var app = this;
    var services = app.services;

    var imports = {};
    if (plugin.consumes) {
        plugin.consumes.forEach(function (name) {
            imports[name] = services[name];
        });
    }

    try {
        // this should be called from the wrapper
        plugin.packageWrapper.setupPlugin(plugin, imports, register);
    } catch(e) {
        return app.emit("error", e);
    }

    function register(err, provided) {
        if (err) {
            return app.emit("error", err);
        }

        plugin.provides.forEach(function (name) {
            if (!provided.hasOwnProperty(name)) {
                var err = new Error("Plugin failed to provide " + name + " service. " + JSON.stringify(plugin));
                return app.emit("error", err);
            }
            services[name] = provided[name];

            if (typeof provided[name] != "function")
                provided[name].name = name;

            app.emit("service", name, services[name]);
        });
        if (provided && provided.hasOwnProperty("onDestroy")) {
            app.destructors.push(provided.onDestroy);
        }

        plugin.destroy = function() {
            if (plugin.provides.length) {
                // @todo, make it possible if all consuming plugins are also dead
                var err = new Error("Plugins that provide services cannot be destroyed. " + JSON.stringify(plugin));
                return app.emit("error", err);
            }

            if (provided && provided.hasOwnProperty("onDestroy")) {
                app.destructors.splice(app.destructors.indexOf(provided.onDestroy), 1);
                provided.onDestroy();
            }

            // delete from config
            app.config.splice(app.config.indexOf(plugin), 1);
            app.emit("destroyed", plugin);
        };

        app.emit("plugin", plugin);
        next();
    }
};

Archie.prototype.getService = function(name) {
    if (!this.services[name]) {
        throw new Error("Service '" + name + "' not found in archie app!");
    }
    return this.services[name];
};

// Returns an event emitter that represents the app.  It can emit events.
// event: ("service" name, service) emitted when a service is ready to be consumed.
// event: ("plugin", plugin) emitted when a plugin registers.
// event: ("ready", app) emitted when all plugins are ready.
// event: ("error", err) emitted when something goes wrong.
// app.services - a hash of all the services in this app
// app.config - the plugin config that was passed in.
function createApp(config, callback) {
    var app;
    try {
        app = new Archie(config);
    } catch(err) {
        if (!callback) throw err;
        return callback(err, app);
    }
    if (callback) {
        app.on("error", done);
        app.on("ready", onReady);
    }
    return app;

    function onReady(app) {
        done();
    }

    function done(err) {
        if (err) {
            app.destroy();
        }
        app.removeListener("error", done);
        app.removeListener("ready", onReady);
        callback(err, app);
    }

}

module.exports = exports;
<commit_msg>removed support for async loading (required by browser) and fixed a bug in loading a new module.<commit_after>"use strict";

var events = require('events');
var EventEmitter = events.EventEmitter;

var Wrappers = require('./wrappers');

var exports = {};

// Only define Node-style usage using sync I/O if in node.
(function () {
    var dirname = require('path').dirname;
    var resolve = require('path').resolve;
    var exists = require('fs').exists || require('path').exists;
    var realpath = require('fs').realpath;
    var exists = require('fs').exists || require('path').exists;
    var realpath = require('fs').realpath;
    var packagePathCache = {};

    exports.loadConfig = loadConfig;
    exports.resolveConfig = resolveConfig;

    // This is assumed to be used at startup and uses sync I/O as well as can
    // throw exceptions.  It loads and parses a config file.
    function loadConfig(configPath, callback) {
      var config = require(configPath);
      var base = dirname(configPath);

      var ext_config = resolveDefaultWrappers(config, base, callback);
      return resolveConfig(ext_config, base, callback);
    }

    function resolveConfig(config, base) {
        config.forEach(function (plugin, index) {
            // Shortcut where string is used for plugin without any options.
            if (typeof plugin === "string") {
                plugin = config[index] = { packagePath: plugin };
            }

            // The plugin is a package on the disk.  We need to load it.
            if (plugin.hasOwnProperty("packagePath")
                  && !(plugin.hasOwnProperty("setup") || plugin.hasOwnProperty("provides"))) {
                var defaults = resolveModule(base, plugin.packagePath);
                Object.keys(defaults).forEach(function (key) {
                    if (!plugin.hasOwnProperty(key)) {
                        plugin[key] = defaults[key];
                    }
                });

                // if no, don't load setup function, etc and create hooks
                plugin.packagePath = defaults.packagePath; // now has the file to load

                // not all packages have a main entry point
                // in which case require fails.
                try{
                    plugin.setup = require(plugin.packagePath);
                } catch(e){}

                resolveWrappedServices(base, plugin);
            }
        });
        return config;
    }

    // Loads a module, getting metadata from either it's package.json or export
    // object.
    function resolveModule(base, modulePath) {
        var packagePath;
        try {
            packagePath = resolvePackage(base, modulePath + "/package.json");
        }
        catch (err) {
            if (err.code !== "ENOENT") throw err;
        }
        var packageJson = packagePath && require(packagePath) || {};
        var metadata = packagePath && packageJson.plugin || {};
        if (packagePath) {
            modulePath = dirname(packagePath);
        } else
        if (!packageJson) { // gets path to index.js, etc
            modulePath = resolvePackage(base, modulePath);
        }

        // pacakgeJson - may not have a main entry point
        // in which case these functions are not needed.
        var module = {};
        if(packageJson.main){
            module = require(modulePath);
        }
        metadata.provides = metadata.provides || module.provides || [];
        metadata.consumes = metadata.consumes || module.consumes || [];
        metadata.packagePath = modulePath;
        return metadata;
    }

    // Node style package resolving so that plugins' package.json can be found relative to the config file
    // It's not the full node require system algorithm, but it's the 99% case
    // This throws, make sure to wrap in try..catch
    function resolvePackage(base, packagePath) {
        var originalBase = base;
        if (!(base in packagePathCache)) {
            packagePathCache[base] = {};
        }
        var cache = packagePathCache[base];
        if (packagePath in cache) {
            return cache[packagePath];
        }
        var newPath, newBase;
        if (packagePath[0] === "." || packagePath[0] === "/") {
            newPath = resolve(base, packagePath);
            if (exists(newPath)) {
                newPath = realpath(newPath);
                cache[packagePath] = newPath;
                return newPath;
            }
        }
        else {
            while (base) {
                newPath = resolve(base, "node_modules", packagePath);
                if (exists(newPath)) {
                    newPath = realpath(newPath);
                    cache[packagePath] = newPath;
                    return newPath;
                }
                newBase = resolve(base, '..');
                if (base === newBase) {
                    break;
                }
                base = newBase;
            }
        }
        var err = new Error("Can't find '" + packagePath + "' relative to '" + originalBase + "'");
        err.code = "ENOENT";
        throw err;
    }

    // Load all objects that are provided by the module (they will be consumed by wrapper)
    function resolveWrappedServices(base, plugin){
        if(!plugin.packageWrapper){
            plugin.packageWrapper = Wrappers.defaultWrapperName;
        }
        var wrapperInst = Wrappers.newWrapper(plugin.packageWrapper);
        wrapperInst.resolveConfig(plugin, base);
        plugin.packageWrapper = wrapperInst;
    }
}());

exports.createApp = createApp;
exports.Archie = Archie;

// Check a plugin config list for bad dependencies and throw on error
function checkConfig(config) {

    // Check for the required fields in each plugin.
    config.forEach(function (plugin) {
        if (plugin.checked) { return; }
        // legacy: not all packages have setup functions
        //if (!plugin.hasOwnProperty("setup")) {
        //    throw new Error("Plugin is missing the setup function " + JSON.stringify(plugin));
        //}
        if (!plugin.hasOwnProperty("provides")) {
            throw new Error("Plugin is missing the provides array " + JSON.stringify(plugin));
        }
        if (!plugin.hasOwnProperty("consumes")) {
            throw new Error("Plugin is missing the consumes array " + JSON.stringify(plugin));
        }
    });

    return checkCycles(config);
}

function checkCycles(config) {
    var plugins = [];
    config.forEach(function(pluginConfig, index) {
        plugins.push({
            packagePath: pluginConfig.packagePath,
            provides: pluginConfig.provides.concat(),
            consumes: pluginConfig.consumes.concat(),
            i: index
        });
    });

    var resolved = {
        hub: true
    };
    var changed = true;
    var sorted = [];

    while(plugins.length && changed) {
        changed = false;

        plugins.concat().forEach(function(plugin) {
            var consumes = plugin.consumes.concat();

            var resolvedAll = true;
            for (var i=0; i<consumes.length; i++) {
                var service = consumes[i];
                if (!resolved[service]) {
                    resolvedAll = false;
                } else {
                    plugin.consumes.splice(plugin.consumes.indexOf(service), 1);
                }
            }

            if (!resolvedAll)
                return;

            plugins.splice(plugins.indexOf(plugin), 1);
            plugin.provides.forEach(function(service) {
                resolved[service] = true;
            });
            sorted.push(config[plugin.i]);
            changed = true;
        });
    }

    if (plugins.length) {
        var unresolved = {};
        plugins.forEach(function(plugin) {
            delete plugin.config;
            plugin.consumes.forEach(function(name) {
                if (unresolved[name] == false)
                    return;
                if (!unresolved[name])
                    unresolved[name] = [];
                unresolved[name].push(plugin.packagePath);
            });
            plugin.provides.forEach(function(name) {
                unresolved[name] = false;
            });
        });

        Object.keys(unresolved).forEach(function(name) {
            if (unresolved[name] == false)
                delete unresolved[name];
        });

        console.error("Could not resolve dependencies of these plugins:", plugins);
        console.error("Resolved services:", Object.keys(resolved));
        console.error("Missing services:", unresolved);
        throw new Error("Could not resolve dependencies");
    }

    return sorted;
}

function Archie(config) {
    var app = this;
    app.config = [];
    app.destructors = [];
    app.services = {
        hub: {
            on: function (name, callback) {
                app.on(name, callback);
            }
        }
    };

    // Give createApp some time to subscribe to our "ready" event
    (typeof process === "object" ? process.nextTick : setTimeout)(function() {
        app.loadPlugins(config, function(err) {
            if (err) {
                throw err;
            }
            app.emit("ready", app);
        });
    });
}

Archie.prototype = Object.create(EventEmitter.prototype, {constructor:{value:Archie}});

Archie.prototype.destroy = function() {
    var app = this;

    app.destructors.forEach(function(destroy) {
        destroy();
    });

    app.destructors = [];
};

Archie.prototype.loadPlugins = function(config, callback) {
    var app = this;

    var sortedConfig;
    try {
        sortedConfig = checkConfig(config.concat(app.config));
    }
    catch(ex) {
        return callback(ex);
    }

    // prevent double loading of plugins
    sortedConfig = sortedConfig.filter(function(c) {
        return config.indexOf(c) > -1;
    });

    var p;
    function next(err) {
        if (err) {
            return callback(err);
        }
        if (p && app.config.indexOf(p) === -1) {
            app.config.push(p);
        }

        p = sortedConfig.shift();
        if (!p) {
            return callback();
        }
        app.registerPlugin(p, next);
    }
    next();
};

/**
 * Register a plugin in the service
 */
Archie.prototype.registerPlugin = function(plugin, next) {
    var app = this;
    var services = app.services;

    var imports = {};
    if (plugin.consumes) {
        plugin.consumes.forEach(function (name) {
            imports[name] = services[name];
        });
    }

    try {
        // this should be called from the wrapper
        plugin.packageWrapper.setupPlugin(plugin, imports, register);
    } catch(e) {
        return app.emit("error", e);
    }

    function register(err, provided) {
        if (err) {
            return app.emit("error", err);
        }

        plugin.provides.forEach(function (name) {
            if (!provided.hasOwnProperty(name)) {
                var err = new Error("Plugin failed to provide " + name + " service. " + JSON.stringify(plugin));
                return app.emit("error", err);
            }
            services[name] = provided[name];

            if (typeof provided[name] != "function")
                provided[name].name = name;

            app.emit("service", name, services[name]);
        });
        if (provided && provided.hasOwnProperty("onDestroy")) {
            app.destructors.push(provided.onDestroy);
        }

        plugin.destroy = function() {
            if (plugin.provides.length) {
                // @todo, make it possible if all consuming plugins are also dead
                var err = new Error("Plugins that provide services cannot be destroyed. " + JSON.stringify(plugin));
                return app.emit("error", err);
            }

            if (provided && provided.hasOwnProperty("onDestroy")) {
                app.destructors.splice(app.destructors.indexOf(provided.onDestroy), 1);
                provided.onDestroy();
            }

            // delete from config
            app.config.splice(app.config.indexOf(plugin), 1);
            app.emit("destroyed", plugin);
        };

        app.emit("plugin", plugin);
        next();
    }
};

Archie.prototype.getService = function(name) {
    if (!this.services[name]) {
        throw new Error("Service '" + name + "' not found in archie app!");
    }
    return this.services[name];
};

// Returns an event emitter that represents the app.  It can emit events.
// event: ("service" name, service) emitted when a service is ready to be consumed.
// event: ("plugin", plugin) emitted when a plugin registers.
// event: ("ready", app) emitted when all plugins are ready.
// event: ("error", err) emitted when something goes wrong.
// app.services - a hash of all the services in this app
// app.config - the plugin config that was passed in.
function createApp(config, callback) {
    var app;
    try {
        app = new Archie(config);
    } catch(err) {
        if (!callback) throw err;
        return callback(err, app);
    }
    if (callback) {
        app.on("error", done);
        app.on("ready", onReady);
    }
    return app;

    function onReady(app) {
        done();
    }

    function done(err) {
        if (err) {
            app.destroy();
        }
        app.removeListener("error", done);
        app.removeListener("ready", onReady);
        callback(err, app);
    }

}

module.exports = exports;
<|endoftext|>


<commit_before>/*jslint browser: true, undef: true *//*global Ext*/
Ext.define('Site.widget.Login', {
    singleton: true,
    requires: [
        'Ext.DomHelper'
    ],

    config: {
        loginLinkSelector: 'a[href^="/login"]',
        loginModalId: 'login-modal'
    },

    constructor: function(config) {
        var me = this;

        me.callParent(arguments);
        me.initConfig(config);

        Ext.onReady(me.onDocReady, me);
    },

    onDocReady: function() {
        var me = this,
            body = Ext.getBody(),
            loginModal = me.loginModal = Ext.get(me.getLoginModalId()),
            loginForm = me.loginForm = loginModal && loginModal.down('form');

        if (!loginModal) {
            return;
        }

        loginModal.enableDisplayMode();

        body.on('keyup', 'onBodyKeyup', me);
        body.on('click', 'onLoginLinkClick', me, {delegate: me.getLoginLinkSelector()});
        loginModal.down('.modal-close-button').on('click', 'hide', me);
        loginForm.on('submit', 'onLoginSubmit', me);
    },

    hide: function() {
        this.loginModal.hide();
        Ext.getBody().removeCls('blurred');
    },

    show: function() {
        Ext.getBody().addCls('blurred');
        this.loginModal.show();
    },

    onBodyKeyup: function(ev, t) {
        if (ev.getKey() == ev.ESC) {
            this.hide();
        }
    },

    onLoginLinkClick: function(ev, t) {
        var me = this;

        ev.preventDefault();
        me.show();
        me.loginForm.down('input[autofocus]').focus();
    },

    onLoginSubmit: function(ev, t) {
        var loginForm = this.loginForm;

        ev.preventDefault();
        loginForm.addCls('waiting');

        Ext.Ajax.request({
            url: '/login/json',
            method: 'POST',
            form: loginForm,
            success: function(response) {
                window.location.reload();
            },
            failure: function(response) {
                loginForm.dom.submit();
            }
        });
    }
});<commit_msg>use delegate and modern json flag for modal login<commit_after>/*jslint browser: true, undef: true *//*global Ext*/
Ext.define('Site.widget.Login', {
    singleton: true,
    requires: [
        'Ext.DomHelper'
    ],

    config: {
        loginLinkSelector: 'a[href^="/login"]',
        loginModalId: 'login-modal'
    },

    constructor: function(config) {
        var me = this;

        me.callParent(arguments);
        me.initConfig(config);

        Ext.onReady(me.onDocReady, me);
    },

    onDocReady: function() {
        var me = this,
            body = Ext.getBody(),
            loginModal = me.loginModal = Ext.get(me.getLoginModalId()),
            loginForm = me.loginForm = loginModal && loginModal.down('form');

        if (!loginModal) {
            return;
        }

        loginModal.enableDisplayMode();

        body.on('keyup', 'onBodyKeyup', me);
        body.on('click', 'onLoginLinkClick', me, { delegate: me.getLoginLinkSelector() });
        loginModal.on('click', 'hide', me, { delegate: '[data-action="close"]' });
        loginForm.on('submit', 'onLoginSubmit', me);
    },

    hide: function() {
        this.loginModal.hide();
        Ext.getBody().removeCls('blurred');
    },

    show: function() {
        Ext.getBody().addCls('blurred');
        this.loginModal.show();
    },

    onBodyKeyup: function(ev, t) {
        if (ev.getKey() == ev.ESC) {
            this.hide();
        }
    },

    onLoginLinkClick: function(ev, t) {
        var me = this;

        ev.preventDefault();
        me.show();
        me.loginForm.down('input[autofocus]').focus();
    },

    onLoginSubmit: function(ev, t) {
        var loginForm = this.loginForm;

        ev.preventDefault();
        loginForm.addCls('waiting');

        Ext.Ajax.request({
            url: '/login/?format=json',
            method: 'POST',
            form: loginForm,
            success: function(response) {
                window.location.reload();
            },
            failure: function(response) {
                loginForm.dom.submit();
            }
        });
    }
});<|endoftext|>