<commit_before>
const RG = require('./rg');

RG.Factory = require('./factory');
RG.Factory.Battle = require('./factory.battle');
const OW = require('./overworld.map');
const Menu = require('./menu');

const debug = require('debug')('bitn:GameMaster');

/* GameMaster objects reacts to various events caused by player and other
 * actors, and shapes the game world based on them. For example,
 * GameMaster can:
 *   - start battles on the area player entered
 *   - spawn special events and actors
 *   - spawn special items etc.
 */
const GameMaster = function(game) {
    this.player = null;
    this.game = game;
    this.fact = new RG.Factory.Battle(game);

    // Lookup table for battles by level ID
    this.battles = {};

    this.setBattles = battles => {
        this.battles = battles;
    };

    this.battlesDone = {};

    this.setPool = pool => {this.pool = pool;};
    this.setGame = game => {this.game = game;};

    this.setPlayer = player => {
        this.player = player;
    };
    this.setWorld = world => {this.world = world;};

    this.hasNotify = true;
    this.notify = (evtName, args) => {
        if (evtName === RG.EVT_LEVEL_CHANGED) {
            debug('EVT_LEVEL_CHANGED');
            const {actor} = args;
            if (actor.isPlayer()) {
                if (!actor.has('InBattle')) {
                    this.addPlayerToBattle(args);
                }
                else {
                    this.removePlayerFromBattle(args);
                }
            }
        }
        else if (evtName === RG.EVT_TILE_CHANGED) {
            debug('EVT_TILE_CHANGED');
            // Should spawn battle etc event
            if (!this.player) {
                debug('\tBut player is NULL for the moment');
                return;
            }
            debug('\tPlayer not null. Creating battle');
            const parentLevel = this.player.getLevel();
            const parentId = parentLevel.getID();

            const ow = this.game.getOverWorld();
            let maxDanger = 4;
            let armySize = 20;
            const battleConf = {};
            let levelType = 'forest';

            let bbox = parentLevel.getBbox();

            if (ow) {
                const world = this.game.getCurrentWorld();
                const area = world.getAreas()[0];
                const xy = area.findTileXYById(parentId);

                // TODO use actual starting position
                const startX = 2;
                const startY = area.getSizeY() - 1;
                const dX = Math.abs(startX - xy[0]);
                const dY = Math.abs(startY - xy[1]);
                maxDanger += dX + dY;
                armySize += 20 * dY + 10 * dX;

                const msg = `dx,dy: ${dX},${dY} armySize ${armySize}`;
                debug(`${msg} , danger: ${maxDanger}`);

                const owPos = this.game.getPlayerOwPos();
                const biome = ow.getBiome(owPos[0], owPos[1]);
                levelType = this.biomeToLevelType(biome);
                debug('Creating battle on tile ' + xy);
                bbox = this.getLevelBbox(ow, area, xy, owPos, parentLevel);
                console.log('Got bbox ' + JSON.stringify(bbox));
            }
            battleConf.maxDanger = maxDanger;
            battleConf.armySize = armySize;
            battleConf.levelType = levelType;
            battleConf.bbox = bbox;

            if (!this.battles.hasOwnProperty(parentId)) {
                this.battles[parentId] = [];
                const battle = this.fact.createBattle(parentLevel, battleConf);
                this.addBattle(parentId, battle);
                this.game.addBattle(this.getBattle(parentId), parentId);
            }
        }
        else if (evtName === RG.EVT_EXPLORED_ZONE_LEFT) {
            // TODO check for creating a new battle on the map
            RG.diag('Explored zone left');
        }
        else if (evtName === RG.EVT_BATTLE_OVER) {
            const {battle} = args;
            debug(`EVT_BATTLE_OVER: ${battle.getName()}`);
            const id = battle.getLevel().getID();

            debug(`1. battlesDone for ${id}: ${this.battlesDone[id]}`);
            if (!this.battlesDone[id] && battle) {
                debug(`2. battlesDone for ${id}: ${this.battlesDone[id]}`);
                this.battlesDone[id] = true;
                debug(`3. battlesDone for ${id}: ${this.battlesDone[id]}`);
                this.addBadgesForActors(battle);
                this.moveActorsOutOfBattle(battle);
                const bName = battle.getName();
                RG.gameMsg(`Battle ${bName} is over!`);
            }
            else {
                const json = JSON.stringify(args);
                RG.err('Game.Master', 'notify',
                    `Args ${json} does not contain "battle"`);
            }
            debug('GameMaster registered battle over');
            // TODO delete the battle (but keep the level)
        }
    };
    RG.POOL.listenEvent(RG.EVT_LEVEL_CHANGED, this);
    RG.POOL.listenEvent(RG.EVT_TILE_CHANGED, this);
    RG.POOL.listenEvent(RG.EVT_BATTLE_OVER, this);

    /* Returns the bbox for the battle. This is coordinates for the battle
     * inside the tile level. It corresponds to player's current owPos. */
    this.getLevelBbox = function(ow, area, tileXY, owPos, level) {
        // Info needed:
        // local ow pos
        // one ow pos in level cells
        const [owSizeX, owSizeY] = [ow.getSizeX(), ow.getSizeY()];
        const [owX, owY] = owPos;
        const [areaX, areaY] = [area.getSizeX(), area.getSizeY()];
        const [cols, rows] = level.getColsRows();

        const owTilesPerAreaTileX = owSizeX / areaX;
        const owTilesPerAreaTileY = owSizeY / areaY;
        const oneOwTileInCols = areaX * cols / owSizeX;
        const oneOwTileInRows = areaY * rows / owSizeY;
        const localOwX = owX / owTilesPerAreaTileX;
        const localOwY = owY / owTilesPerAreaTileY;

        return {
            ulx: localOwX * oneOwTileInCols,
            uly: localOwY * oneOwTileInRows,
            lrx: (localOwX + 1) * oneOwTileInCols - 1,
            lry: (localOwY + 1) * oneOwTileInRows - 1
        };
    };

    /* Adds player to the battle level. */
    this.addPlayerToBattle = function(args) {
        const {actor, target, src} = args;
        const srcID = src.getID();
        if (this.battles.hasOwnProperty(srcID)) {
            const battle = this.getBattle(srcID);
            if (battle.isJSON) {
                return; // Cannot join serialized battle anyway
            }
            const battleLevel = battle.getLevel();
            if (battleLevel.getID() === target.getID()) {
                if (this.actorCanEnter(actor, battle)) {
                    // Entered a battle
                    const comp = new RG.Component.InBattle();
                    comp.setData({name: battle.getName()});
                    actor.add(comp);
                    // Get army selection object
                    const obj = this.getSelArmyObject(actor, battle);
                    actor.getBrain().setSelectionObject(obj);
                }
                else if (battle.isOver()) {
                    RG.gameMsg('Looks like the battle is already fought..');
                }
                else {
                    RG.gameMsg('You cannot join the fight anymore, deserter.');
                }
            }
        }

    };

    this.addBattle = function(parentId, battle) {
        this.battles[parentId].push(battle);
    };

    this.getBattle = function(parentId) {
        const battle = this.battles[parentId][0];
        return battle;
    };

    this.getBattles = function(parentId) {
        return this.battles[parentId];
    };

    /* Returns true if the actor can still enter the battle as an army member.
     * */
    this.actorCanEnter = (actor, battle) => {
        if (battle.isOver()) {return false;}
        if (this.actorDesertedBattle(actor, battle)) {return false;}
        return true;
    };

    /* Removes the player from a battle. */
    this.removePlayerFromBattle = function(args) {
        const {actor, target, src} = args;
        const areaID = target.getID();
        const srcID = src.getID();
        const battle = this.getBattle(areaID);
        const battleLevID = battle.getLevel().getID();

        const inBattleComp = actor.get('InBattle');
        const battleData = inBattleComp.getData();

        if (srcID !== battleLevID) {
            const msg = `Level ID mismatch: ${srcID} !== ${battleLevID}`;
            RG.err('GameMaster', 'removePlayerFromBattle', msg);
        }

        // Mark player as deserter, TODO add confirm object
        if (!battle.isOver() && battleData.army) {
            const badge = new RG.Component.BattleBadge();
            badge.setData({status: 'Fled', name: battle.getName(),
                army: battleData.army});
            actor.add(badge);
            actor.remove('InBattle');
            actor.add(new RG.Component.BattleOver());
        }
    };

    /* Adds BattleBadges after a battle is over. */
    this.addBadgesForActors = battle => {
        const armies = battle.getArmies();
        armies.forEach(army => {
            const actors = army.getActors();
            const ids = actors.map(actor => actor.getID());

            actors.forEach(actor => {
                if (!this.actorDesertedBattle(actor, battle)) {
                    const badge = new RG.Component.BattleBadge();
                    const battleData = {
                        name: battle.getName(),
                        army: army.getName(),
                        allies: ids,
                        status: army.isDefeated() ? 'Lost' : 'Won'
                    };
                    badge.setData(battleData);
                    actor.add(badge);

                    actor.remove('InBattle');
                    actor.add(new RG.Component.BattleOver());
                }
            });
        });
    };

    this.actorDesertedBattle = (actor, battle) => {
        const badgeList = actor.getList('BattleBadge');
        const badge = badgeList.find(b => (
            b.getData().name === battle.getName()
        ));
        if (badge) {return true;}
        return false;
    };

    /* Moves actors out of the battle level into the parent level of the battle
     * (at the moment this is always Area.Tile level. */
    this.moveActorsOutOfBattle = battle => {
        const level = battle.getLevel();
        const conns = level.getConnections();

        if (!conns || conns.length === 0) {
            RG.err('Game.Master', 'moveActorsOutOfBattle',
                'No exit connnection in level');
        }

        const exit = conns[0];
        const targetLevel = exit.getTargetLevel();

        const armies = battle.getArmies();
        armies.forEach(army => {
            const actors = army.getActors();
            actors.forEach(actor => {
                if (actor.isInLevel(level)) {

                    if (!actor.isPlayer()) {
                        if (level.removeActor(actor)) {
                            targetLevel.addActorToFreeCell(actor);
                        }
                        else {
                            const json = JSON.stringify(actor.toJSON());
                            RG.err('Game.Master', 'moveActorsOutOfBattle',
                                `level.removeActor failed for actor ${json}`);
                        }

                    }
                    else {
                        const selObj = this.getSelLeaveBattle(actor, level);
                        actor.getBrain().setSelectionObject(selObj);
                    }
                }
            });
        });

    };

    /* Returns the selection object for player to select an army. */
    this.getSelArmyObject = function(player, battle) {
        const armies = battle.getArmies();
        const selArmyFunc = selection => {
            const army = armies[selection];
            const battleLevel = battle.getLevel();
            let armyActors = army.getActors();
            const nActors = armyActors.length;

            const pIndex = RG.RAND.getUniformInt(0, nActors - 1);
            const replacedActor = armyActors[pIndex];
            const [pX, pY] = replacedActor.getXY();

            // Remove substituted actor from army/level
            replacedActor.get('Action').disable();
            army.removeActor(replacedActor);
            battleLevel.removeActor(replacedActor);

            armyActors = army.getActors();
            army.addActor(player);

            player.get('InBattle').updateData({army: army.getName});
            armyActors.forEach(actor => {
                actor.addFriend(player);
            });

            armies.forEach(enemyArmy => {
                if (enemyArmy !== army) {
                    const enemies = enemyArmy.getActors();
                    enemies.forEach(enemy => {
                        enemy.addEnemy(player);
                    });
                }
            });

            if (!battleLevel.moveActorTo(player, pX, pY)) {
                RG.err('GameMaster', 'getSelArmyObject',
                    `Could not move player to ${pX},${pY}`);
            }
        };

        const choices = armies.map((army, i) => {
            return [' Army ' + army.getName(), selArmyFunc.bind(this, i)];
        });
        choices.push(['Take no side', Menu.EXIT_MENU]);
        const menu = new Menu.SelectRequired(choices);
        menu.addPre('Please select an army to join:');
        return menu;
    };

    this.getSelLeaveBattle = function(player, level) {
        const leaveFunc = () => {
          const exit = level.getConnections()[0];
          if (!exit.useStairs(player)) {
            RG.err('GameMaster', 'moveActorsOutOfBattle',
              'Cannot move player via useStairs');
          }
          else {
            const name = player.getName();
            RG.gameMsg(`${name} leaves the battlefield`);
          }
        };
        const choices = [
          ['Leave immediately', leaveFunc],
          ['Stay behind to scavenge the bodies of the dead.', Menu.EXIT_MENU]
        ];
        const menu = new Menu.SelectRequired(choices);
        menu.addPre('Battle is over! Do you want to leave battle?');
        return menu;
    };

    /* Serializes the object into JSON. */
    this.toJSON = function() {
        const keys = Object.keys(this.battles);
        const battles = {};
        keys.forEach(id => {
            const battlesTile = this.getBattles(id);
            battlesTile.forEach(battle => {
                if (battles.hasOwnProperty(id)) {
                    RG.warn('Game.Master', 'toJSON',
                        `Battle for ID ${id} exists already`);
                }
                else {
                    battles[id] = [];
                }

                if (typeof battle.toJSON === 'function') {
                    battles[id].push(battle.toJSON());
                }
                else if (battle.name) {
                    battles[id].push(battle);
                }
                else {
                    RG.err('GameMaster', 'toJSON',
                        'Does not look like proper battle object.');
                }
            });
        });
        return {
            battles,
            battlesDone: this.battlesDone
        };
    };

    /* Used by the ChunkManager to serialize the battle when player move far
     * enough from the tile. */
    this.unloadBattles = tileLevel => {
        const id = tileLevel.getID();
        if (this.battles.hasOwnProperty(id)) {
            const battles = this.getBattles(id);
            this.battles[id] = [];
            battles.forEach(battle => {
                if (typeof battle.toJSON === 'function') {
                    if (!battle.isOver()) {
                        // Important, otherwise cannot be GC'd
                        battle.removeListeners();
                    }
                    this.battles[id].push(battle.toJSON());
                }
                else {
                    RG.err('GameMaster', 'unloadBattle',
                        `Unload for level ${id} failed`);
                }
            });
        }
    };

    this.biomeToLevelType = function(biome) {
        switch (biome) {
            case OW.BIOME.ARCTIC: return 'arctic';
            case OW.BIOME.TUNDRA: return 'arctic';
            case OW.BIOME.ALPINE: return 'mountain';
            case OW.BIOME.TAIGA: return 'forest';
            default: return 'forest';
        }
    };

    this.getBattleLevels = function() {
        const levels = [];
        Object.values(this.battles).forEach(battle => {
            levels.push(battle.getLevel());
        });
        return levels;

    };
};

module.exports = GameMaster;
<commit_msg>Corrected the local OW position calculations to use % instead of /.<commit_after>
const RG = require('./rg');

RG.Factory = require('./factory');
RG.Factory.Battle = require('./factory.battle');
const OW = require('./overworld.map');
const Menu = require('./menu');

const debug = require('debug')('bitn:GameMaster');

/* GameMaster objects reacts to various events caused by player and other
 * actors, and shapes the game world based on them. For example,
 * GameMaster can:
 *   - start battles on the area player entered
 *   - spawn special events and actors
 *   - spawn special items etc.
 */
const GameMaster = function(game) {
    this.player = null;
    this.game = game;
    this.fact = new RG.Factory.Battle(game);

    // Lookup table for battles by level ID
    this.battles = {};

    this.setBattles = battles => {
        this.battles = battles;
    };

    this.battlesDone = {};

    this.setPool = pool => {this.pool = pool;};
    this.setGame = game => {this.game = game;};

    this.setPlayer = player => {
        this.player = player;
    };
    this.setWorld = world => {this.world = world;};

    this.hasNotify = true;
    this.notify = (evtName, args) => {
        if (evtName === RG.EVT_LEVEL_CHANGED) {
            debug('EVT_LEVEL_CHANGED');
            const {actor} = args;
            if (actor.isPlayer()) {
                if (!actor.has('InBattle')) {
                    this.addPlayerToBattle(args);
                }
                else {
                    this.removePlayerFromBattle(args);
                }
            }
        }
        else if (evtName === RG.EVT_TILE_CHANGED) {
            debug('EVT_TILE_CHANGED');
            // Should spawn battle etc event
            if (!this.player) {
                debug('\tBut player is NULL for the moment');
                return;
            }
            debug('\tPlayer not null. Creating battle');
            const parentLevel = this.player.getLevel();
            const parentId = parentLevel.getID();

            const ow = this.game.getOverWorld();
            let maxDanger = 4;
            let armySize = 20;
            const battleConf = {};
            let levelType = 'forest';

            let bbox = parentLevel.getBbox();

            if (ow) {
                const world = this.game.getCurrentWorld();
                const area = world.getAreas()[0];
                const xy = area.findTileXYById(parentId);

                // TODO use actual starting position
                const startX = 2;
                const startY = area.getSizeY() - 1;
                const dX = Math.abs(startX - xy[0]);
                const dY = Math.abs(startY - xy[1]);
                maxDanger += dX + dY;
                armySize += 20 * dY + 10 * dX;

                const msg = `dx,dy: ${dX},${dY} armySize ${armySize}`;
                debug(`${msg} , danger: ${maxDanger}`);

                const owPos = this.game.getPlayerOwPos();
                const biome = ow.getBiome(owPos[0], owPos[1]);
                levelType = this.biomeToLevelType(biome);
                debug('Creating battle on tile ' + xy);
                bbox = this.getLevelBbox(ow, area, xy, owPos, parentLevel);
            }
            battleConf.maxDanger = maxDanger;
            battleConf.armySize = armySize;
            battleConf.levelType = levelType;
            battleConf.bbox = bbox;

            if (!this.battles.hasOwnProperty(parentId)) {
                this.battles[parentId] = [];
                const battle = this.fact.createBattle(parentLevel, battleConf);
                this.addBattle(parentId, battle);
                this.game.addBattle(this.getBattle(parentId), parentId);
            }
        }
        else if (evtName === RG.EVT_EXPLORED_ZONE_LEFT) {
            // TODO check for creating a new battle on the map
            RG.diag('Explored zone left');
        }
        else if (evtName === RG.EVT_BATTLE_OVER) {
            const {battle} = args;
            debug(`EVT_BATTLE_OVER: ${battle.getName()}`);
            const id = battle.getLevel().getID();

            debug(`1. battlesDone for ${id}: ${this.battlesDone[id]}`);
            if (!this.battlesDone[id] && battle) {
                debug(`2. battlesDone for ${id}: ${this.battlesDone[id]}`);
                this.battlesDone[id] = true;
                debug(`3. battlesDone for ${id}: ${this.battlesDone[id]}`);
                this.addBadgesForActors(battle);
                this.moveActorsOutOfBattle(battle);
                const bName = battle.getName();
                RG.gameMsg(`Battle ${bName} is over!`);
            }
            else {
                const json = JSON.stringify(args);
                RG.err('Game.Master', 'notify',
                    `Args ${json} does not contain "battle"`);
            }
            debug('GameMaster registered battle over');
            // TODO delete the battle (but keep the level)
        }
    };
    RG.POOL.listenEvent(RG.EVT_LEVEL_CHANGED, this);
    RG.POOL.listenEvent(RG.EVT_TILE_CHANGED, this);
    RG.POOL.listenEvent(RG.EVT_BATTLE_OVER, this);

    /* Returns the bbox for the battle. This is coordinates for the battle
     * inside the tile level. It corresponds to player's current owPos. */
    this.getLevelBbox = function(ow, area, tileXY, owPos, level) {
        // Info needed:
        // local ow pos
        // one ow pos in level cells
        const [owSizeX, owSizeY] = [ow.getSizeX(), ow.getSizeY()];
        const [owX, owY] = owPos;
        const [areaX, areaY] = [area.getSizeX(), area.getSizeY()];
        const [cols, rows] = level.getColsRows();

        const owTilesPerAreaTileX = owSizeX / areaX;
        const owTilesPerAreaTileY = owSizeY / areaY;
        const oneOwTileInCols = areaX * cols / owSizeX;
        const oneOwTileInRows = areaY * rows / owSizeY;
        const localOwX = owX % owTilesPerAreaTileX;
        const localOwY = owY % owTilesPerAreaTileY;

        return {
            ulx: localOwX * oneOwTileInCols,
            uly: localOwY * oneOwTileInRows,
            lrx: (localOwX + 1) * oneOwTileInCols - 1,
            lry: (localOwY + 1) * oneOwTileInRows - 1
        };
    };

    /* Adds player to the battle level. */
    this.addPlayerToBattle = function(args) {
        const {actor, target, src} = args;
        const srcID = src.getID();
        if (this.battles.hasOwnProperty(srcID)) {
            const battle = this.getBattle(srcID);
            if (battle.isJSON) {
                return; // Cannot join serialized battle anyway
            }
            const battleLevel = battle.getLevel();
            if (battleLevel.getID() === target.getID()) {
                if (this.actorCanEnter(actor, battle)) {
                    // Entered a battle
                    const comp = new RG.Component.InBattle();
                    comp.setData({name: battle.getName()});
                    actor.add(comp);
                    // Get army selection object
                    const obj = this.getSelArmyObject(actor, battle);
                    actor.getBrain().setSelectionObject(obj);
                }
                else if (battle.isOver()) {
                    RG.gameMsg('Looks like the battle is already fought..');
                }
                else {
                    RG.gameMsg('You cannot join the fight anymore, deserter.');
                }
            }
        }

    };

    this.addBattle = function(parentId, battle) {
        this.battles[parentId].push(battle);
    };

    this.getBattle = function(parentId) {
        const battle = this.battles[parentId][0];
        return battle;
    };

    this.getBattles = function(parentId) {
        return this.battles[parentId];
    };

    /* Returns true if the actor can still enter the battle as an army member.
     * */
    this.actorCanEnter = (actor, battle) => {
        if (battle.isOver()) {return false;}
        if (this.actorDesertedBattle(actor, battle)) {return false;}
        return true;
    };

    /* Removes the player from a battle. */
    this.removePlayerFromBattle = function(args) {
        const {actor, target, src} = args;
        const areaID = target.getID();
        const srcID = src.getID();
        const battle = this.getBattle(areaID);
        const battleLevID = battle.getLevel().getID();

        const inBattleComp = actor.get('InBattle');
        const battleData = inBattleComp.getData();

        if (srcID !== battleLevID) {
            const msg = `Level ID mismatch: ${srcID} !== ${battleLevID}`;
            RG.err('GameMaster', 'removePlayerFromBattle', msg);
        }

        // Mark player as deserter, TODO add confirm object
        if (!battle.isOver() && battleData.army) {
            const badge = new RG.Component.BattleBadge();
            badge.setData({status: 'Fled', name: battle.getName(),
                army: battleData.army});
            actor.add(badge);
            actor.remove('InBattle');
            actor.add(new RG.Component.BattleOver());
        }
    };

    /* Adds BattleBadges after a battle is over. */
    this.addBadgesForActors = battle => {
        const armies = battle.getArmies();
        armies.forEach(army => {
            const actors = army.getActors();
            const ids = actors.map(actor => actor.getID());

            actors.forEach(actor => {
                if (!this.actorDesertedBattle(actor, battle)) {
                    const badge = new RG.Component.BattleBadge();
                    const battleData = {
                        name: battle.getName(),
                        army: army.getName(),
                        allies: ids,
                        status: army.isDefeated() ? 'Lost' : 'Won'
                    };
                    badge.setData(battleData);
                    actor.add(badge);

                    actor.remove('InBattle');
                    actor.add(new RG.Component.BattleOver());
                }
            });
        });
    };

    this.actorDesertedBattle = (actor, battle) => {
        const badgeList = actor.getList('BattleBadge');
        const badge = badgeList.find(b => (
            b.getData().name === battle.getName()
        ));
        if (badge) {return true;}
        return false;
    };

    /* Moves actors out of the battle level into the parent level of the battle
     * (at the moment this is always Area.Tile level. */
    this.moveActorsOutOfBattle = battle => {
        const level = battle.getLevel();
        const conns = level.getConnections();

        if (!conns || conns.length === 0) {
            RG.err('Game.Master', 'moveActorsOutOfBattle',
                'No exit connnection in level');
        }

        const exit = conns[0];
        const targetLevel = exit.getTargetLevel();

        const armies = battle.getArmies();
        armies.forEach(army => {
            const actors = army.getActors();
            actors.forEach(actor => {
                if (actor.isInLevel(level)) {

                    if (!actor.isPlayer()) {
                        if (level.removeActor(actor)) {
                            targetLevel.addActorToFreeCell(actor);
                        }
                        else {
                            const json = JSON.stringify(actor.toJSON());
                            RG.err('Game.Master', 'moveActorsOutOfBattle',
                                `level.removeActor failed for actor ${json}`);
                        }

                    }
                    else {
                        const selObj = this.getSelLeaveBattle(actor, level);
                        actor.getBrain().setSelectionObject(selObj);
                    }
                }
            });
        });

    };

    /* Returns the selection object for player to select an army. */
    this.getSelArmyObject = function(player, battle) {
        const armies = battle.getArmies();
        const selArmyFunc = selection => {
            const army = armies[selection];
            const battleLevel = battle.getLevel();
            let armyActors = army.getActors();
            const nActors = armyActors.length;

            const pIndex = RG.RAND.getUniformInt(0, nActors - 1);
            const replacedActor = armyActors[pIndex];
            const [pX, pY] = replacedActor.getXY();

            // Remove substituted actor from army/level
            replacedActor.get('Action').disable();
            army.removeActor(replacedActor);
            battleLevel.removeActor(replacedActor);

            armyActors = army.getActors();
            army.addActor(player);

            player.get('InBattle').updateData({army: army.getName});
            armyActors.forEach(actor => {
                actor.addFriend(player);
            });

            armies.forEach(enemyArmy => {
                if (enemyArmy !== army) {
                    const enemies = enemyArmy.getActors();
                    enemies.forEach(enemy => {
                        enemy.addEnemy(player);
                    });
                }
            });

            if (!battleLevel.moveActorTo(player, pX, pY)) {
                RG.err('GameMaster', 'getSelArmyObject',
                    `Could not move player to ${pX},${pY}`);
            }
        };

        const choices = armies.map((army, i) => {
            return [' Army ' + army.getName(), selArmyFunc.bind(this, i)];
        });
        choices.push(['Take no side', Menu.EXIT_MENU]);
        const menu = new Menu.SelectRequired(choices);
        menu.addPre('Please select an army to join:');
        return menu;
    };

    this.getSelLeaveBattle = function(player, level) {
        const leaveFunc = () => {
          const exit = level.getConnections()[0];
          if (!exit.useStairs(player)) {
            RG.err('GameMaster', 'moveActorsOutOfBattle',
              'Cannot move player via useStairs');
          }
          else {
            const name = player.getName();
            RG.gameMsg(`${name} leaves the battlefield`);
          }
        };
        const choices = [
          ['Leave immediately', leaveFunc],
          ['Stay behind to scavenge the bodies of the dead.', Menu.EXIT_MENU]
        ];
        const menu = new Menu.SelectRequired(choices);
        menu.addPre('Battle is over! Do you want to leave battle?');
        return menu;
    };

    /* Serializes the object into JSON. */
    this.toJSON = function() {
        const keys = Object.keys(this.battles);
        const battles = {};
        keys.forEach(id => {
            const battlesTile = this.getBattles(id);
            battlesTile.forEach(battle => {
                if (battles.hasOwnProperty(id)) {
                    RG.warn('Game.Master', 'toJSON',
                        `Battle for ID ${id} exists already`);
                }
                else {
                    battles[id] = [];
                }

                if (typeof battle.toJSON === 'function') {
                    battles[id].push(battle.toJSON());
                }
                else if (battle.name) {
                    battles[id].push(battle);
                }
                else {
                    RG.err('GameMaster', 'toJSON',
                        'Does not look like proper battle object.');
                }
            });
        });
        return {
            battles,
            battlesDone: this.battlesDone
        };
    };

    /* Used by the ChunkManager to serialize the battle when player move far
     * enough from the tile. */
    this.unloadBattles = tileLevel => {
        const id = tileLevel.getID();
        if (this.battles.hasOwnProperty(id)) {
            const battles = this.getBattles(id);
            this.battles[id] = [];
            battles.forEach(battle => {
                if (typeof battle.toJSON === 'function') {
                    if (!battle.isOver()) {
                        // Important, otherwise cannot be GC'd
                        battle.removeListeners();
                    }
                    this.battles[id].push(battle.toJSON());
                }
                else {
                    RG.err('GameMaster', 'unloadBattle',
                        `Unload for level ${id} failed`);
                }
            });
        }
    };

    this.biomeToLevelType = function(biome) {
        switch (biome) {
            case OW.BIOME.ARCTIC: return 'arctic';
            case OW.BIOME.TUNDRA: return 'arctic';
            case OW.BIOME.ALPINE: return 'mountain';
            case OW.BIOME.TAIGA: return 'forest';
            default: return 'forest';
        }
    };

    this.getBattleLevels = function() {
        const levels = [];
        Object.values(this.battles).forEach(battle => {
            levels.push(battle.getLevel());
        });
        return levels;

    };
};

module.exports = GameMaster;
<|endoftext|>


<commit_before>const path = require('path')
const webpack = require('webpack')

const {
  sassLoaderVersion
} = require('@dcloudio/uni-cli-shared/lib/scss')

const {
  getPartialIdentifier
} = require('./util')

function resolve (dir) {
  return path.resolve(__dirname, '..', dir)
}

module.exports = function chainWebpack (platformOptions, vueOptions, api) {
  const {
    runByHBuilderX, // 使用 HBuilderX 运行
    cssPreprocessOptions
  } = require('@dcloudio/uni-cli-shared')

  return function (webpackConfig) {
    // 处理静态资源 limit
    const urlLoader = require('@dcloudio/uni-cli-shared/lib/url-loader')
    const staticTypes = ['images', 'media', 'fonts']
    staticTypes.forEach(staticType => {
      const newOptions = urlLoader.options()
      if (webpack.version[0] > 4) {
        if ('limit' in newOptions) {
          webpackConfig.module.rule(staticType).parser({
            dataUrlCondition: {
              maxSize: newOptions.limit
            }
          })
        }
      } else {
        webpackConfig.module
          .rule(staticType)
          .use('url-loader')
          .loader(urlLoader.loader)
          .tap(options => Object.assign(options, newOptions))
      }
    })
    // 条件编译 vue 文件统一直接过滤html,js,css三种类型,单独资源文件引用各自过滤

    const loaders = {
      scss: 'sass-loader',
      sass: 'sass-loader',
      less: 'less-loader',
      stylus: 'stylus-loader'
    }
    // 独立css,postcss,scss,sass,less,stylus
    const cssLang = ['css', 'postcss', 'scss', 'sass', 'less', 'stylus']

    const cssTypes = ['vue-modules', 'vue', 'normal-modules', 'normal']

    cssLang.forEach(lang => {
      const langRule = webpackConfig.module.rule(lang)
      const loader = loaders[lang]
      cssTypes.forEach(type => {
        if (process.env.UNI_USING_CACHE) {
          langRule.oneOf(type)
            .use('uniapp-cache-css')
            .loader('cache-loader')
            .options(api.genCacheConfig(
              'css-loader/' + process.env.UNI_PLATFORM,
              getPartialIdentifier()
            ))
            .before('css-loader')
        }
        langRule.oneOf(type)
          .use('uniapp-preprocss')
          .loader(resolve('packages/webpack-preprocess-loader'))
          .options(cssPreprocessOptions)
          .after('css-loader') // 在 css-loader 之前条件编译一次

        if (loader) { // 在 scss,less,stylus 之前先条件编译一次（似乎没有必要了，保证css-loader处理一次即可，前提是条件编译注释都还存在）
          langRule.oneOf(type)
            .use('uniapp-preprocss-' + lang)
            .loader(resolve('packages/webpack-preprocess-loader'))
            .options(cssPreprocessOptions)
            .after(loader)
        }
      })
    })

    if (sassLoaderVersion >= 8) { // check indentedSyntax
      // vue cli 3 and sass-loader 8
      cssTypes.forEach(type => {
        webpackConfig.module.rule('sass').oneOf(type).use('sass-loader').tap(options => {
          if (options.indentedSyntax) {
            if (!options.sassOptions) {
              options.sassOptions = {}
            }
            options.sassOptions.indentedSyntax = true
            delete options.indentedSyntax
          }
          return options
        })
      })
    }

    platformOptions.chainWebpack(webpackConfig, vueOptions, api)
    // define
    const deferredCreated = process.env.UNI_PLATFORM === 'mp-toutiao' ||
      process.env.UNI_PLATFORM === 'quickapp-webview'
    const defines = {
      // UNI_ENV好像没用
      __UNI_FEATURE_PROMISE__: JSON.stringify(false),
      'process.env.UNI_ENV': JSON.stringify(process.env.UNI_PLATFORM),
      'process.env.UNI_APP_ID': JSON.stringify(process.env.UNI_APP_ID),
      'process.env.UNI_APP_NAME': JSON.stringify(process.env.UNI_APP_NAME),
      'process.env.UNI_PLATFORM': JSON.stringify(process.env.UNI_PLATFORM),
      'process.env.UNI_SUB_PLATFORM': JSON.stringify(process.env.UNI_SUB_PLATFORM),
      'process.env.UNI_CLOUD_PROVIDER': process.env.UNI_CLOUD_PROVIDER,
      'process.env.UNICLOUD_DEBUG': process.env.UNICLOUD_DEBUG,
      'process.env.RUN_BY_HBUILDERX': process.env.RUN_BY_HBUILDERX,
      'process.env.UNI_AUTOMATOR_WS_ENDPOINT': JSON.stringify(process.env.UNI_AUTOMATOR_WS_ENDPOINT),
      'process.env.UNI_STATISTICS_CONFIG': process.env.UNI_STATISTICS_CONFIG,
      'process.env.UNI_STAT_UNI_CLOUD': process.env.UNI_STAT_UNI_CLOUD,
      'process.env.UNI_STAT_DEBUG': process.env.UNI_STAT_DEBUG,
      'process.env.UNI_COMPILER_VERSION': JSON.stringify(process.env.UNI_COMPILER_VERSION),
      'process.env.UNI_APP_VERSION_NAME': JSON.stringify(process.env.UNI_APP_VERSION_NAME),
      'process.env.UNI_APP_VERSION_CODE': JSON.stringify(process.env.UNI_APP_VERSION_CODE)
    }
    if (process.env.UNI_USING_VUE3) {
      Object.assign(defines, {
        __UNI_WX_API__: JSON.stringify(process.env.UNI_USING_WX_API === 'true'),
        __UNI_WXS_API__: JSON.stringify(process.env.UNI_USING_WXS_API === 'true'),
        __UNI_PROMISE_API__: JSON.stringify(process.env.UNI_USING_PROMISE_API === 'true'),
        __VUE_OPTIONS_API__: JSON.stringify(process.env.UNI_USING_VUE3_OPTIONS_API === 'true'),
        __VUE_CREATED_DEFERRED__: JSON.stringify(deferredCreated),
        __VUE_PROD_DEVTOOLS__: JSON.stringify(false)
      })
    }
    if (process.env.UNI_PLATFORM === 'h5') {
      // TODO manifest.json
      defines.__UNI_ROUTER_MODE__ = JSON.stringify('hash')
    }

    webpackConfig
      .plugin('uni-define')
      .use(require.resolve('webpack/lib/DefinePlugin'), [defines])

    if (runByHBuilderX) { // 由 HBuilderX 运行时，移除进度，错误
      webpackConfig.plugins.delete('progress')
      webpackConfig.plugins.delete('friendly-errors')
    } else {
      webpackConfig.plugin('friendly-errors')
        .tap(args => {
          if (global.__error_reporting__) {
            args[0].onErrors = function (severity, errors) {
              if (severity !== 'error') {
                return
              }
              const error = errors[0]
              global.__error_reporting__ && global.__error_reporting__(error.name, error.message || '')
            }
          }
          return args
        })
    }
    if (process.env.BUILD_ENV === 'ali-ide') {
      webpackConfig.plugins.delete('progress')
    }
    webpackConfig.resolve.alias
      .delete('@')
      .set(
        '@/pages.json',
        path.resolve(process.env.UNI_INPUT_DIR, 'pages.json') +
        '?' + JSON.stringify({
          type: 'origin-pages-json'
        })
      )
      .set('@', path.resolve(process.env.UNI_INPUT_DIR))
  }
}
<commit_msg>fix: webpack5 static file path<commit_after>const path = require('path')
const webpack = require('webpack')

const {
  sassLoaderVersion
} = require('@dcloudio/uni-cli-shared/lib/scss')

const {
  getPartialIdentifier
} = require('./util')

function resolve (dir) {
  return path.resolve(__dirname, '..', dir)
}

module.exports = function chainWebpack (platformOptions, vueOptions, api) {
  const {
    runByHBuilderX, // 使用 HBuilderX 运行
    cssPreprocessOptions
  } = require('@dcloudio/uni-cli-shared')

  return function (webpackConfig) {
    // 处理静态资源 limit
    const urlLoader = require('@dcloudio/uni-cli-shared/lib/url-loader')
    const staticTypes = ['images', 'media', 'fonts']
    staticTypes.forEach(staticType => {
      const newOptions = urlLoader.options()
      if (webpack.version[0] > 4) {
        if ('limit' in newOptions) {
          webpackConfig.module.rule(staticType).parser({
            dataUrlCondition: {
              maxSize: newOptions.limit
            }
          })
        }
        if (newOptions.fallback && newOptions.fallback.options) {
          const generator = {}
          const oldOptions = newOptions.fallback.options
          const keys = ['publicPath', 'outputPath']
          keys.forEach(key => {
            generator[key] = pathData => {
              const outputPath = oldOptions.outputPath(null, pathData.module.request)
              const basename = path.basename(outputPath)
              return outputPath.substring(0, outputPath.length - basename.length)
            }
          })
          generator.filename = pathData => {
            return path.basename(pathData.module.request)
          }
          webpackConfig.module.rule(staticType).set('generator', generator)
        }
      } else {
        webpackConfig.module
          .rule(staticType)
          .use('url-loader')
          .loader(urlLoader.loader)
          .tap(options => Object.assign(options, newOptions))
      }
    })
    // 条件编译 vue 文件统一直接过滤html,js,css三种类型,单独资源文件引用各自过滤

    const loaders = {
      scss: 'sass-loader',
      sass: 'sass-loader',
      less: 'less-loader',
      stylus: 'stylus-loader'
    }
    // 独立css,postcss,scss,sass,less,stylus
    const cssLang = ['css', 'postcss', 'scss', 'sass', 'less', 'stylus']

    const cssTypes = ['vue-modules', 'vue', 'normal-modules', 'normal']

    cssLang.forEach(lang => {
      const langRule = webpackConfig.module.rule(lang)
      const loader = loaders[lang]
      cssTypes.forEach(type => {
        if (process.env.UNI_USING_CACHE) {
          langRule.oneOf(type)
            .use('uniapp-cache-css')
            .loader('cache-loader')
            .options(api.genCacheConfig(
              'css-loader/' + process.env.UNI_PLATFORM,
              getPartialIdentifier()
            ))
            .before('css-loader')
        }
        if (webpack.version[0] > 4) {
          langRule.oneOf(type)
            .use('css-loader')
            .tap(options => {
              options.url = {
                filter: function (url) {
                  return url[0] !== '/'
                }
              }
              return options
            })
        }
        langRule.oneOf(type)
          .use('uniapp-preprocss')
          .loader(resolve('packages/webpack-preprocess-loader'))
          .options(cssPreprocessOptions)
          .after('css-loader') // 在 css-loader 之前条件编译一次

        if (loader) { // 在 scss,less,stylus 之前先条件编译一次（似乎没有必要了，保证css-loader处理一次即可，前提是条件编译注释都还存在）
          langRule.oneOf(type)
            .use('uniapp-preprocss-' + lang)
            .loader(resolve('packages/webpack-preprocess-loader'))
            .options(cssPreprocessOptions)
            .after(loader)
        }
      })
    })

    if (sassLoaderVersion >= 8) { // check indentedSyntax
      // vue cli 3 and sass-loader 8
      cssTypes.forEach(type => {
        webpackConfig.module.rule('sass').oneOf(type).use('sass-loader').tap(options => {
          if (options.indentedSyntax) {
            if (!options.sassOptions) {
              options.sassOptions = {}
            }
            options.sassOptions.indentedSyntax = true
            delete options.indentedSyntax
          }
          return options
        })
      })
    }

    platformOptions.chainWebpack(webpackConfig, vueOptions, api)
    // define
    const deferredCreated = process.env.UNI_PLATFORM === 'mp-toutiao' ||
      process.env.UNI_PLATFORM === 'quickapp-webview'
    const defines = {
      // UNI_ENV好像没用
      __UNI_FEATURE_PROMISE__: JSON.stringify(false),
      'process.env.UNI_ENV': JSON.stringify(process.env.UNI_PLATFORM),
      'process.env.UNI_APP_ID': JSON.stringify(process.env.UNI_APP_ID),
      'process.env.UNI_APP_NAME': JSON.stringify(process.env.UNI_APP_NAME),
      'process.env.UNI_PLATFORM': JSON.stringify(process.env.UNI_PLATFORM),
      'process.env.UNI_SUB_PLATFORM': JSON.stringify(process.env.UNI_SUB_PLATFORM),
      'process.env.UNI_CLOUD_PROVIDER': process.env.UNI_CLOUD_PROVIDER,
      'process.env.UNICLOUD_DEBUG': process.env.UNICLOUD_DEBUG,
      'process.env.RUN_BY_HBUILDERX': process.env.RUN_BY_HBUILDERX,
      'process.env.UNI_AUTOMATOR_WS_ENDPOINT': JSON.stringify(process.env.UNI_AUTOMATOR_WS_ENDPOINT),
      'process.env.UNI_STATISTICS_CONFIG': process.env.UNI_STATISTICS_CONFIG,
      'process.env.UNI_STAT_UNI_CLOUD': process.env.UNI_STAT_UNI_CLOUD,
      'process.env.UNI_STAT_DEBUG': process.env.UNI_STAT_DEBUG,
      'process.env.UNI_COMPILER_VERSION': JSON.stringify(process.env.UNI_COMPILER_VERSION),
      'process.env.UNI_APP_VERSION_NAME': JSON.stringify(process.env.UNI_APP_VERSION_NAME),
      'process.env.UNI_APP_VERSION_CODE': JSON.stringify(process.env.UNI_APP_VERSION_CODE)
    }
    if (process.env.UNI_USING_VUE3) {
      Object.assign(defines, {
        __UNI_WX_API__: JSON.stringify(process.env.UNI_USING_WX_API === 'true'),
        __UNI_WXS_API__: JSON.stringify(process.env.UNI_USING_WXS_API === 'true'),
        __UNI_PROMISE_API__: JSON.stringify(process.env.UNI_USING_PROMISE_API === 'true'),
        __VUE_OPTIONS_API__: JSON.stringify(process.env.UNI_USING_VUE3_OPTIONS_API === 'true'),
        __VUE_CREATED_DEFERRED__: JSON.stringify(deferredCreated),
        __VUE_PROD_DEVTOOLS__: JSON.stringify(false)
      })
    }
    if (process.env.UNI_PLATFORM === 'h5') {
      // TODO manifest.json
      defines.__UNI_ROUTER_MODE__ = JSON.stringify('hash')
    }

    webpackConfig
      .plugin('uni-define')
      .use(require.resolve('webpack/lib/DefinePlugin'), [defines])

    if (runByHBuilderX) { // 由 HBuilderX 运行时，移除进度，错误
      webpackConfig.plugins.delete('progress')
      webpackConfig.plugins.delete('friendly-errors')
    } else {
      webpackConfig.plugin('friendly-errors')
        .tap(args => {
          if (global.__error_reporting__) {
            args[0].onErrors = function (severity, errors) {
              if (severity !== 'error') {
                return
              }
              const error = errors[0]
              global.__error_reporting__ && global.__error_reporting__(error.name, error.message || '')
            }
          }
          return args
        })
    }
    if (process.env.BUILD_ENV === 'ali-ide') {
      webpackConfig.plugins.delete('progress')
    }
    webpackConfig.resolve.alias
      .delete('@')
      .set(
        '@/pages.json',
        path.resolve(process.env.UNI_INPUT_DIR, 'pages.json') +
        '?' + JSON.stringify({
          type: 'origin-pages-json'
        })
      )
      .set('@', path.resolve(process.env.UNI_INPUT_DIR))
  }
}
<|endoftext|>


<commit_before>document.addEventListener("DOMContentLoaded", function(event){
	var editorDiv = document.getElementById("editor")
	var editor = new Quill(editorDiv)
	var postBodyInput = document.getElementById("post-body")
	var validityInput = document.getElementById("post-validity")
	var tagsInput = document.getElementById("tags-input")
	var placeholderTagsInput = document.getElementById("placeholder-tags-input")
	var tagsInputPlugin = insignia(tagsInput);
	var form = document.getElementById("post-form")
	var linkButton = document.getElementById("add-link")
	var imageButton = document.getElementById("add-image")
	var linkModalElement = document.getElementById("link-modal")
	var linkModal = new Modal(linkModalElement)
	var linkModalInput = linkModalElement.querySelector("input#modal-link")
	var linkModalError = linkModalElement.querySelector("div.modal-error")
	var imageModalElement = document.getElementById("image-modal")
	var imageModal = new Modal(imageModalElement)
	var imageModalLinkInput = imageModalElement.querySelector("input#image-url")
	var imageModalFileInput = imageModalElement.querySelector("input#image-upload")
	var alignLeft = document.getElementById("align-left")
	var alignCenter = document.getElementById("align-center")
	var alignRight = document.getElementById("align-right")
	var alignJustify = document.getElementById("align-justify")

	editor.addModule("toolbar", {container: "div#toolbar"})
	console.log(editor)
	editor.on("selection-change", function(range){
		if (range == null){
			editorDiv.classList.remove("focused")
		}

		else {
			editorDiv.classList.add("focused")
		}
	})

	linkButton.addEventListener("click", function(event){
		//Once the input is focused, we will lose our selection. We need to get it now.
		linkModal.show()
		linkModalInput.focus()
	})

	imageButton.addEventListener("click", function(event){
		imageModal.show()
	})

	alignLeft.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatLine(selection.start, selection.end, "align", "left")
		}
	})

	alignCenter.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatLine(selection.start, selection.end, "align", "center")
		}
	})

	alignRight.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatLine(selection.start, selection.end, "align", "right")
		}
	})

	alignJustify.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatText(selection.start, selection.end+1, "align", "justify")
			console.log(selection)
			console.log("formatted")
		}
	})

	linkModal.element.addEventListener("positive-pressed", function(event){
		if (linkModalInput.value.trim().length === 0) {
			linkModalError.classList.add("active")
			event.preventDefault();
		}
		else {
			editor.focus()
			var selection = editor.getSelection()
			editor.setSelection(null)
			//This basically indicates that we don't actually have a selection.
			if (selection.end - selection.start === 0){
				editor.insertText(selection.start, linkModalInput.value, "link", linkModalInput.value)
			}
		}
	})

	linkModal.element.addEventListener("hide", function(event){
		linkModalInput.value = ""
		linkModalError.classList.remove("active")
	})

	imageModalLinkInput.addEventListener("input", function(event){
		imageModalFileInput.disabled = true
	})

	imageModalFileInput.addEventListener("change", function(event){
		imageModalLinkInput.disabled = true
	})

	imageModal.element.addEventListener("hide", function(event){
		imageModalLinkInput.disabled = false
		imageModalLinkInput.value = ""
		imageModalFileInput.disabled = false
		imageModalFileInput.value = null
	})

	tagsInput.addEventListener("focus", function(event){
		var div = document.getElementById("tag-input-div")
		div.classList.add("focused")
	});

	tagsInput.addEventListener("blur", function(event){
		var div = document.getElementById("tag-input-div")
		div.classList.remove("focused")
	});

	form.addEventListener("submit", function(event){
		if (editor.getText().trim().length == 0) {
			validityInput.setCustomValidity("Please fill out a post body.")
			event.preventDefault();
		}
		else {
			postBodyInput.value = editor.getHTML();
			placeholderTagsInput.value = tagsInputPlugin.value()
		}
	})

})

<commit_msg>Remove debug console.logs<commit_after>document.addEventListener("DOMContentLoaded", function(event){
	var editorDiv = document.getElementById("editor")
	var editor = new Quill(editorDiv)
	var postBodyInput = document.getElementById("post-body")
	var validityInput = document.getElementById("post-validity")
	var tagsInput = document.getElementById("tags-input")
	var placeholderTagsInput = document.getElementById("placeholder-tags-input")
	var tagsInputPlugin = insignia(tagsInput);
	var form = document.getElementById("post-form")
	var linkButton = document.getElementById("add-link")
	var imageButton = document.getElementById("add-image")
	var linkModalElement = document.getElementById("link-modal")
	var linkModal = new Modal(linkModalElement)
	var linkModalInput = linkModalElement.querySelector("input#modal-link")
	var linkModalError = linkModalElement.querySelector("div.modal-error")
	var imageModalElement = document.getElementById("image-modal")
	var imageModal = new Modal(imageModalElement)
	var imageModalLinkInput = imageModalElement.querySelector("input#image-url")
	var imageModalFileInput = imageModalElement.querySelector("input#image-upload")
	var alignLeft = document.getElementById("align-left")
	var alignCenter = document.getElementById("align-center")
	var alignRight = document.getElementById("align-right")
	var alignJustify = document.getElementById("align-justify")

	editor.addModule("toolbar", {container: "div#toolbar"})
	editor.on("selection-change", function(range){
		if (range == null){
			editorDiv.classList.remove("focused")
		}

		else {
			editorDiv.classList.add("focused")
		}
	})

	linkButton.addEventListener("click", function(event){
		//Once the input is focused, we will lose our selection. We need to get it now.
		linkModal.show()
		linkModalInput.focus()
	})

	imageButton.addEventListener("click", function(event){
		imageModal.show()
	})

	alignLeft.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatLine(selection.start, selection.end, "align", "left")
		}
	})

	alignCenter.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatLine(selection.start, selection.end, "align", "center")
		}
	})

	alignRight.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatLine(selection.start, selection.end, "align", "right")
		}
	})

	alignJustify.addEventListener("click", function(event){
		editor.focus()
		var selection = editor.getSelection()
		if (selection != null){
			editor.formatText(selection.start, selection.end+1, "align", "justify")
		}
	})

	linkModal.element.addEventListener("positive-pressed", function(event){
		if (linkModalInput.value.trim().length === 0) {
			linkModalError.classList.add("active")
			event.preventDefault();
		}
		else {
			editor.focus()
			var selection = editor.getSelection()
			editor.setSelection(null)
			//This basically indicates that we don't actually have a selection.
			if (selection.end - selection.start === 0){
				editor.insertText(selection.start, linkModalInput.value, "link", linkModalInput.value)
			}
		}
	})

	linkModal.element.addEventListener("hide", function(event){
		linkModalInput.value = ""
		linkModalError.classList.remove("active")
	})

	imageModalLinkInput.addEventListener("input", function(event){
		imageModalFileInput.disabled = true
	})

	imageModalFileInput.addEventListener("change", function(event){
		imageModalLinkInput.disabled = true
	})

	imageModal.element.addEventListener("hide", function(event){
		imageModalLinkInput.disabled = false
		imageModalLinkInput.value = ""
		imageModalFileInput.disabled = false
		imageModalFileInput.value = null
	})

	tagsInput.addEventListener("focus", function(event){
		var div = document.getElementById("tag-input-div")
		div.classList.add("focused")
	});

	tagsInput.addEventListener("blur", function(event){
		var div = document.getElementById("tag-input-div")
		div.classList.remove("focused")
	});

	form.addEventListener("submit", function(event){
		if (editor.getText().trim().length == 0) {
			validityInput.setCustomValidity("Please fill out a post body.")
			event.preventDefault();
		}
		else {
			postBodyInput.value = editor.getHTML();
			placeholderTagsInput.value = tagsInputPlugin.value()
		}
	})

})

<|endoftext|>


<commit_before>
module.exports = function(grunt) {
  var path = require('path');

  var SOURCE_FILES = [
    'src/ts/**/*.ts',
    '!src/ts/types/**'
  ]

  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    /**
     * 1 - Transpile all TypeScript sources to ES6
     */
    ts: {
      default: {
        src: SOURCE_FILES,
        outDir: '_build/phase1',
        options: {
          target: 'es6',
          comments: true
        }
      }
    },

    /**
     * 2 - Compile the app sources to a single bundle using
     * browserify, running babeljs over the sources on the way.
     */
    browserify: {

      dep: {
        files: {
          '_build/dependencies.js' : [
            'src/js/dependencies.js'
          ]
        },
        options: {
          browserifyOptions: {
            // debug: true
          }
        }
      },

      app: {
        files: {
          '_build/phase2.js' : [
            '_build/phase1/**/*.js'
          ]
        },
        options: {
          transform: [
            'babelify'
          ],
          browserifyOptions: {
            // debug: true
          }
        }
      }
    },

    /**
     * 3 - Precompile the handlebars templates
     */
    handlebars: {
      all: {
        options: {
          partialsUseNamespace: true,
          namespace: function(filename) {
            return path.dirname(filename).split('/').join('.').replace('src', 'ts')
          },
          processName: function(filename) {
            var pieces = filename.split('/')
            return pieces[pieces.length - 1].split('.')[0]
          }
        },
        files: {
          '_build/templates.js' : [ 'src/templates/**/*.hbs']
        }
      }
    },

    /**
     * 4 - Concatenate the transpiled sources, the precompiled
     * templates, and the babel polyfill required for all ES6 support.
     */
    concat: {
      dep_css: {
        src: [
          'src/3rd-Party/css/bootstrap.css',
          'src/3rd-Party/css/bootstrap-callouts.css',
          'src/3rd-Party/css/bootstrap-editable.css',
          'src/3rd-Party/css/bootstrap-datetimepicker.css',
          'src/3rd-Party/css/bootstrapValidator.min.css',
          'src/3rd-Party/css/font-awesome.css',
          'src/3rd-Party/css/select2.css',
          'src/3rd-Party/css/select2-bootstrap.css',
          'src/3rd-Party/css/dataTables.bootstrap.css',
          'src/3rd-Party/css/jquery.flot.valuelabels.css',
          'src/3rd-Party/css/highlight-styles/github.css'
        ],
        dest: 'tessera/static/css/bundle.css'
      },
      dep_js: {
        options: {
          separator: ';'
        },
        src: [
          '_build/dependencies.js',
          'src/3rd-Party/js/tagmanager.js',
          'src/3rd-Party/js/bootstrap-editable.min.js',
          'src/3rd-Party/js/bootstrap-datetimepicker.min.js',
          'src/3rd-Party/js/bootstrapValidator.min.js',
          'src/3rd-Party/js/flot/jquery.flot.js',
          'src/3rd-Party/js/flot/jquery.flot.time.js',
          'src/3rd-Party/js/flot/jquery.flot.multihighlight.js',
          'src/3rd-Party/js/flot/jquery.flot.d3.stack.js',
          'src/3rd-Party/js/flot/jquery.flot.crosshair.js',
          'src/3rd-Party/js/flot/jquery.flot.axislabels.js',
          'src/3rd-Party/js/flot/jquery.flot.downsample.js',
          'src/3rd-Party/js/flot/jquery.flot.valuelabels.js',
          'src/3rd-Party/js/flot/jquery.flot.pie.js',
          'src/3rd-Party/js/flot/jquery.flot.barnumbers.enhanced.js',
          'src/3rd-Party/js/equalize.min.js'
        ],
        dest: 'tessera/static/bundle.js'
      },
      app: {
        options: {
          separator: ';'
        },
        src: [
          'node_modules/babel-core/browser-polyfill.js',
          '_build/templates.js',
          '_build/phase2.js'
        ],
        dest: 'tessera/static/tessera.js'
      }
    },

    /**
     * 5 - Copy sources to the static resources dir.
     */
    copy: {
      dep: {
        files: [
          { expand: true, cwd: 'src/3rd-Party/fonts/', src: '**', dest: 'tessera/static/fonts/' },
          { expand: true, cwd: 'src/3rd-Party/img/', src: '**', dest: 'tessera/static/img/' },
          {
            expand: true, cwd: 'src/3rd-Party/css/', src: [
              '*.gif', '*.png', '*.map'
            ],
            dest: 'tessera/static/css'
          }
        ]
      },
      app: {
        files: [
          { expand: true, cwd: 'src/css/', src: '**', dest: 'tessera/static/' }
        ]
      }
    },

    clean: {
      build: {
        src: [
          '_build/',
          'tessera/static/*'
        ]
      }
    },

    watch: {
      dep: {
        files: [
          'src/js/dependencies.js',
          'src/3rd-Party/**/*.*'
        ],
        tasks: ['dep']
      },
      app_templates: {
        files: [
          'src/templates/**/*.hbs'
        ],
        tasks: ['handlebars', 'concat:app', 'copy:app']
      },
      app: {
        files: [
          'src/ts/**/*.ts',
          'src/css/*.css'
        ],
        tasks: ['app']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-handlebars');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-ts-1.5');

  /** Compile the tessera source and templates */
  grunt.registerTask('app', [
    'ts', 'browserify:app', 'handlebars', 'concat:app', 'copy:app'
  ])

  /** Compile all third-party dependencies */
  grunt.registerTask('dep', [
    'browserify:dep', 'concat:dep_js', 'concat:dep_css', 'copy:dep'
  ]);

  /** Compile everything */
  grunt.registerTask('all', [
    'app', 'dep'
  ])

  /** By default */
  grunt.registerTask('default', [
    'all'
  ])
}
<commit_msg>Better watch targets<commit_after>
module.exports = function(grunt) {
  var path = require('path');

  var SOURCE_FILES = [
    'src/ts/**/*.ts',
    '!src/ts/types/**'
  ]

  grunt.initConfig({

    pkg: grunt.file.readJSON('package.json'),

    /**
     * 1 - Transpile all TypeScript sources to ES6
     */
    ts: {
      default: {
        src: SOURCE_FILES,
        outDir: '_build/phase1',
        options: {
          target: 'es6',
          comments: true
        }
      }
    },

    /**
     * 2 - Compile the app sources to a single bundle using
     * browserify, running babeljs over the sources on the way.
     */
    browserify: {

      dep: {
        files: {
          '_build/dependencies.js' : [
            'src/js/dependencies.js'
          ]
        },
        options: {
          browserifyOptions: {
            // debug: true
          }
        }
      },

      app: {
        files: {
          '_build/phase2.js' : [
            '_build/phase1/**/*.js'
          ]
        },
        options: {
          transform: [
            'babelify'
          ],
          browserifyOptions: {
            // debug: true
          }
        }
      }
    },

    /**
     * 3 - Precompile the handlebars templates
     */
    handlebars: {
      all: {
        options: {
          partialsUseNamespace: true,
          namespace: function(filename) {
            return path.dirname(filename).split('/').join('.').replace('src', 'ts')
          },
          processName: function(filename) {
            var pieces = filename.split('/')
            return pieces[pieces.length - 1].split('.')[0]
          }
        },
        files: {
          '_build/templates.js' : [ 'src/templates/**/*.hbs']
        }
      }
    },

    /**
     * 4 - Concatenate the transpiled sources, the precompiled
     * templates, and the babel polyfill required for all ES6 support.
     */
    concat: {
      dep_css: {
        src: [
          'src/3rd-Party/css/bootstrap.css',
          'src/3rd-Party/css/bootstrap-callouts.css',
          'src/3rd-Party/css/bootstrap-editable.css',
          'src/3rd-Party/css/bootstrap-datetimepicker.css',
          'src/3rd-Party/css/bootstrapValidator.min.css',
          'src/3rd-Party/css/font-awesome.css',
          'src/3rd-Party/css/select2.css',
          'src/3rd-Party/css/select2-bootstrap.css',
          'src/3rd-Party/css/dataTables.bootstrap.css',
          'src/3rd-Party/css/jquery.flot.valuelabels.css',
          'src/3rd-Party/css/highlight-styles/github.css'
        ],
        dest: 'tessera/static/css/bundle.css'
      },
      dep_js: {
        options: {
          separator: ';'
        },
        src: [
          '_build/dependencies.js',
          'src/3rd-Party/js/tagmanager.js',
          'src/3rd-Party/js/bootstrap-editable.min.js',
          'src/3rd-Party/js/bootstrap-datetimepicker.min.js',
          'src/3rd-Party/js/bootstrapValidator.min.js',
          'src/3rd-Party/js/flot/jquery.flot.js',
          'src/3rd-Party/js/flot/jquery.flot.time.js',
          'src/3rd-Party/js/flot/jquery.flot.multihighlight.js',
          'src/3rd-Party/js/flot/jquery.flot.d3.stack.js',
          'src/3rd-Party/js/flot/jquery.flot.crosshair.js',
          'src/3rd-Party/js/flot/jquery.flot.axislabels.js',
          'src/3rd-Party/js/flot/jquery.flot.downsample.js',
          'src/3rd-Party/js/flot/jquery.flot.valuelabels.js',
          'src/3rd-Party/js/flot/jquery.flot.pie.js',
          'src/3rd-Party/js/flot/jquery.flot.barnumbers.enhanced.js',
          'src/3rd-Party/js/equalize.min.js'
        ],
        dest: 'tessera/static/bundle.js'
      },
      app: {
        options: {
          separator: ';'
        },
        src: [
          'node_modules/babel-core/browser-polyfill.js',
          '_build/templates.js',
          '_build/phase2.js'
        ],
        dest: 'tessera/static/tessera.js'
      }
    },

    /**
     * 5 - Copy sources to the static resources dir.
     */
    copy: {
      dep: {
        files: [
          { expand: true, cwd: 'src/3rd-Party/fonts/', src: '**', dest: 'tessera/static/fonts/' },
          { expand: true, cwd: 'src/3rd-Party/img/', src: '**', dest: 'tessera/static/img/' },
          {
            expand: true, cwd: 'src/3rd-Party/css/', src: [
              '*.gif', '*.png', '*.map'
            ],
            dest: 'tessera/static/css'
          }
        ]
      },
      app: {
        files: [
          { expand: true, cwd: 'src/css/', src: '**', dest: 'tessera/static/' }
        ]
      }
    },

    clean: {
      build: {
        src: [
          '_build/',
          'tessera/static/*'
        ]
      }
    },

    watch: {
      dep: {
        files: [
          'src/js/dependencies.js',
          'src/3rd-Party/**/*.*'
        ],
        tasks: ['dep']
      },
      app_templates: {
        files: [
          'src/templates/**/*.hbs'
        ],
        tasks: ['handlebars', 'concat:app', 'copy:app']
      },
      app_js: {
        files: [
          'src/ts/**/*.ts'
        ],
        tasks: ['app']
      },
      app_css: {
        files: [
          'src/css/*.css'
        ],
        tasks: ['copy:app']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-handlebars');
  grunt.loadNpmTasks('grunt-browserify');
  grunt.loadNpmTasks('grunt-ts-1.5');

  /** Compile the tessera source and templates */
  grunt.registerTask('app', [
    'ts', 'browserify:app', 'handlebars', 'concat:app', 'copy:app'
  ])

  /** Compile all third-party dependencies */
  grunt.registerTask('dep', [
    'browserify:dep', 'concat:dep_js', 'concat:dep_css', 'copy:dep'
  ]);

  /** Compile everything */
  grunt.registerTask('all', [
    'app', 'dep'
  ])

  /** By default */
  grunt.registerTask('default', [
    'all'
  ])
}
<|endoftext|>