Source: MapManager.js

/**
 * @typedef {object} MapManager.map
 * @property {boolean|string} north - id of the map north of this map or false
 * @property {boolean|string} south - id of the map south of this map or false
 * @property {boolean|string} east - id of the map eest of this map or false
 * @property {boolean|string} west - id of the map west of this map or false
 * @example
 * {
 *     "north": false,
 *     "south": false,
 *     "east": "Greenhaven",
 *     "west": false
 * }
 */

/**
 * @typedef {object} MapManager.building
 * @property {string} key - texture key of the building
 * @property {number} x - x location of the building
 * @property {number} y - y location of the building
 * @property {function} callback - function called when clicking the building
 * @property {function|boolean} [visible] - If you want to display this building only when a condition is met. The function should return true or false. It is called each time the map is created.
 * @example
 * {
 *     key: "House",
 *     x: 375,
 *     y: 486,
 *     callback: () => {
 *          GAME.openMenu('house');
 *     }
 * }
 * @example
 * {
 *     key: "Brothel",
 *     x: 375,
 *     y: 486,
 *     callback: () => {
 *          GAME.openMenu('brothel');
 *     },
 *     visible: () => {
 *          // This building will only be visible if the townFuckQuest 'Complete' subquest is true
 *          return GAME.quest.isComplete('townFuckQuest','Completed');
 *     }
 * }
 */

/**
 * Use this to manage the map
 * @class MapManager
 */
class MapManager {
    constructor() {
        this._queue = [];
        this._map = {
            "Easthollow": {
                "north": false,
                "south": false,
                "east": 'Greenhaven',
                "west": false
            },
            "Greenhaven": {
                "north": false,
                "south": 'MoaningMorass',
                "east": false,
                "west": 'Easthollow'
            },
            "MoaningMorass": {
                "north": 'Greenhaven',
                "south": 'MoaningMorass2',
                "east": false,
                "west": false
            },
            "MoaningMorass2": {
                "north": 'MoaningMorass',
                "south": 'Crossroad',
                "east": false,
                "west": false
            },
            "Crossroad": {
                "north": "MoaningMorass2",
                "south": false,
                "east": "Avia",
                "west": false
            },
            "Avia": {
                "north": false,
                "south": false,
                "east": false,
                "west": "Crossroad"
            }
        };
        this._mapConditions = {};
        this._buildings = {
            "Easthollow": {
                "House": {
                    key: "House",
                    x: 375,
                    y: 486,
                    callback: () => {
                        GAME.openMenu('house')
                    }
                },
                "Brothel": {
                    key: "Brothel",
                    x: 630,
                    y: 496,
                    callback: () => {
                        GAME.openMenu('brothel')
                    }
                },
                "Town": {
                    key: "Town",
                    x: 1150,
                    y: 134,
                    callback: () => {
                        GAME.map.switchMap('Town');
                    }
                }
            },
            "Town": {
                "ItemShop": {
                    key: "TownItemShop",
                    x: 230,
                    y: 125,
                    callback: () => {
                        GAME.openMenu('townItemShop');
                    }
                },
                "ClothesShop": {
                    key: "TownClothesShop",
                    x: 780,
                    y: 125,
                    callback: () => {
                        GAME.openMenu('townClothesShop');
                    }
                },
                "School": {
                    key: "school",
                    x: 1280,
                    y: 150,
                    callback: () => {
                        GAME.map.switchMap('School');
                    }
                },
                "Geoff": {
                    key: "TownGeoff",
                    x: 100,
                    y: 600,
                    callback: () => {
                        GAME.dialogue.playDialogue('GeoffShop');
                    }
                },
                "Mayor": {
                    key: "TownMayorOffice",
                    x: 1200,
                    y: 600,
                    callback: () => {
                        GAME.dialogue.playDialogue('MayorOffice');
                    }
                },
                "Exit": {
                    key: 'exit',
                    x: 20,
                    y: 963,
                    callback: () => {
                        GAME.map.switchMap('Easthollow');
                    }
                }
            },
            "School": {
                "Cafeteria": {
                    key: "CafeteriaButton",
                    x: 1200,
                    y: 150,
                    callback: () => {
                        GAME.dialogue.playDialogue('SchoolCafeteria');
                    }
                },
                "Janitors": {
                    key: "JanitorsClosetButton",
                    x: 1400,
                    y: 720,
                    callback: () => {
                        GAME.dialogue.playDialogue('janitorsCloset');
                    }
                },
                "Principals": {
                    key: "PrincipalsOfficeButton",
                    x: 800,
                    y: 730,
                    callback: () => {
                        GAME.dialogue.playDialogue('principalsOffice');
                    }
                },
                "Exit": {
                    key: 'exit',
                    x: 20,
                    y: 963,
                    callback: () => {
                        GAME.map.switchMap('Town');
                    }
                }
            },
            "Greenhaven": {
                "Nirvokk": {
                    key: "NirvokkHut",
                    x: 400,
                    y: 200,
                    callback: () => {
                        GAME.dialogue.playDialogue('NirvokkHut');
                    }
                },
                "Darrak": {
                    key: "DarrakHut",
                    x: 1300,
                    y: 350,
                    callback: () => {
                        GAME.dialogue.playDialogue('DarrakHut');
                    }
                },
                "Hangout": {
                    key: "VillageHangout",
                    x: 700,
                    y: 600,
                    callback: () => {
                        GAME.dialogue.playDialogue('VillageHangout');
                    }
                }
            },
            "MoaningMorass": {
                "Inn": {
                    key: "Inn",
                    x: 1500,
                    y: 620,
                    callback: () => {
                        GAME.dialogue.playDialogue('MorassInn');
                    }
                },
                "Pond": {
                    key: "Pond",
                    x: 1450,
                    y: 400,
                    callback: () => {
                        GAME.dialogue.playDialogue('MorassPond');
                    }
                },
                "House1": {
                    key: "House1",
                    x: 345,
                    y: 350,
                    callback: () => {
                        GAME.dialogue.playDialogue('MorassHouse1');
                    }
                },
                "House2": {
                    key: "House2",
                    x: 345,
                    y: 600,
                    callback: () => {
                        GAME.dialogue.playDialogue('MorassHouse2');
                    }
                }
            },
            "MoaningMorass2": {
                "Fungi": {
                    key: "Fungi",
                    x: 1200,
                    y: 260,
                    callback: () => {
                        GAME.dialogue.playDialogue('swampBeastQuestFindFungi');
                    },
                    visible: () => {
                        return GAME.quest.isComplete('swampBeastQuest', 'SeduceGoblin') === true && GAME.quest.isComplete('swampBeastQuest', 'FindFungi') === false;
                    }
                },
                "Boat": {
                    key: "Boat",
                    x: 950,
                    y: 650,
                    callback: () => {
                        GAME.dialogue.playDialogue('swampBeastQuestComplete');
                    },
                    visible: () => {
                        return GAME.quest.isComplete('swampBeastQuest', 'GoblinsAngry') === true && GAME.quest.isComplete('swampBeastQuest', 'Completed') === false;
                    }
                }
            },
            "Crossroad": {},
            "Avia": {
                "Castle": {
                    key: "AviaCastleKeep",
                    x: 500,
                    y: 250,
                    callback: () => {
                        GAME.dialogue.playDialogue('AviaCastleKeep');
                    }
                },
                "Slums": {
                    key: "AviaSlums",
                    x: 400,
                    y: 840,
                    callback: () => {
                        GAME.dialogue.playDialogue('AviaSlums');
                    }
                }
            }
        };

        /**
         * @name MapManager#_arrowConditions
         * @type {Object}
         * @private
         */
        this._arrowConditions = {
            "Easthollow": [function () {
                if (gameData.regions.Greenhaven === false) {
                    GAME.map.disableArrow('east');
                }
            }],
            "Greenhaven": [function () {
                if (gameData.regions.MoaningMorass === false) {
                    GAME.map.disableArrow('south');
                }
            }],
            "MoaningMorass": [function () {
                if (GAME.quest.isComplete('worldQuests', 'morass2Unlock') === false) {
                    GAME.map.disableArrow('south');
                }
            }],
            "MoaningMorass2": [function () {
                if (GAME.quest.isComplete('worldQuests', 'naknuAfterMorassDoor') === false) {
                    GAME.map.disableArrow('south');
                }
            }]
        };

        this._mapGroup = null;

        /**
         * The id of the current map the player is on.
         * @name MapManager#currentMap
         * @type {string}
         */
        this.currentMap = 'Easthollow';
    }

    /**
     * @method getMap
     * @memberOf MapManager
     * @instance
     * @param {string} mapID=undefined - If no id is given, it will return the entire game map
     * @return {MapManager.map|boolean}
     */
    getMap(mapID) {
        if (mapID === undefined) {
            return this._map;
        } else {
            if (this._map.hasOwnProperty(mapID)) {
                return this._map[mapID]
            } else {
                return false;
            }
        }
    }

    /**
     * @getBuildings
     * @memberOf MapManager
     * @param {string} mapID - Get all of the buildings for the map
     * @return {object<MapManager.building>|boolean}
     */
    getBuildings(mapID) {
        if (this._buildings.hasOwnProperty(mapID)) {
            return this._buildings[mapID];
        } else {
            return false;
        }
    }

    /**
     * Add a new building to the map
     * @method addBuilding
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     * @param {string} buildingID
     * @param {MapManager.building} building
     */
    addBuilding(mapID, buildingID, building) {
        if (this._buildings.hasOwnProperty(mapID) === false) {
            this._buildings[mapID] = {};
        }
        this._buildings[mapID][buildingID] = building;
    }

    /**
     * Removes the building from the game entirely
     * @method removeBuilding
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     * @param {string} buildingID
     */
    removeBuilding(mapID, buildingID) {
        delete this._buildings[mapID][buildingID];
    }

    /**
     * @method _addToQueue
     * @memberOf MapManager
     * @instance
     * @private
     * @param {Promise} callback
     */
    _addToQueue(callback) {
        this._queue.push(callback);
    }

    /**
     * @method _playMapConditions
     * @memberOf MapManager
     * @instance
     * @private
     */
    _playMapConditions(currentMap) {
        return new Promise((resolve) => {
            if (this._mapConditions.hasOwnProperty(currentMap)) {
                for (let i in this._mapConditions[currentMap]) {
                    if (typeof this._mapConditions[currentMap][i] === "function") {
                        this._addToQueue(this._mapConditions[currentMap][i]);
                    }
                }
                this._popQueue(resolve);
            } else {
                resolve();
            }
        });
    }

    /**
     * @method _popQueue
     * @memberOf MapManager
     * @instance
     * @private
     * @param {function} resolve
     */
    _popQueue(resolve) {
        if (this._queue.length > 0) {
            let functionType = (this._queue.shift())();
            if (typeof functionType === "object") {
                functionType.then(() => {
                    this._popQueue(resolve);
                })
            } else {
                this._popQueue(resolve);
            }
        } else {
            resolve();
        }
    }

    /**
     * Map conditions are functions called whenever a player goes to that map. For example, when the girls go to the Morass after recruiting Esxea, a dialogue plays. That is a map condition.
     * The callback function should return a promise function if the condition is met to prevent two conditions from playing at the same time.
     * @method addMapCondition
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     * @param {Promise} callback - Should return a promise if the condition is met
     */
    addMapCondition(mapID, callback) {
        if (this._mapConditions.hasOwnProperty(mapID) === false) {
            this._mapConditions[mapID] = [];
        }
        this._mapConditions[mapID].push(callback);
    }

    /**
     * Use {@link MapManager.getMapConditions} to find the index of the condition you want to remove
     * @method removeMapCondition
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     * @param {number} index
     */
    removeMapCondition(mapID, index) {
        this._mapConditions[mapID].splice(index, 1);
    }

    /**
     * Returns the array of map conditions.
     * @method getMapConditions
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     * @return {Array}
     */
    getMapConditions(mapID) {
        return this._mapConditions[mapID];
    }

    /**
     * Switches to a map
     * @method switchMap
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     */
    switchMap(mapID) {
        GAME.map.currentMap = mapID;
        GAME.createMap();
    }

    /**
     * @method _doArrowConditions
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     * @private
     */
    _doArrowConditions(mapID) {
        if (this._arrowConditions.hasOwnProperty(mapID)) {
            for (let i in this._arrowConditions[mapID]) {
                this._arrowConditions[mapID][i]();
            }
        }
    }

    /**
     * Sets the arrow visibility to false.
     * All arrows are visible by default if there is a map location available there. You will only ever need to disable them temporarily for quests or other conditions
     * @method setArrow
     * @memberOf MapManager
     * @instance
     * @param {'north'|'south'|'east'|'west'} direction - Direction of the arrow
     */
    disableArrow(direction) {
        this._mapGroup.getByName(direction).setVisible(false);
    }

    /**
     * Adds an arrow condition to the map. Anything you need to do to the arrows should be inside the callback function. Use {@link MapManager#_arrowConditions} as a template
     * @method setArrowCondition
     * @memberOf MapManager
     * @instance
     * @param {string} mapID
     * @param {function} callback - This function should disable arrows inside of it. Use the default values as a template.
     */
    setArrowCondition(mapID, callback) {
        if (this._arrowConditions.hasOwnProperty(mapID) === false) {
            this._arrowConditions[mapID] = [];
        }
        this._arrowConditions[mapID].push(callback);
    }
}