Source: Game_Map.js

Game_Map.js

//-----------------------------------------------------------------------------
// Game_Map
//
// The game object class for a map. It contains scrolling and passage
// determination functions.
/**
 * The game object class for a map. It contains scrolling and passage determination functions.
 *
 * @class
 */
function Game_Map() {
    this.initialize(...arguments);
}

/**
 * Initialize the map class
 */
Game_Map.prototype.initialize = function() {
    this._interpreter = new Game_Interpreter();
    this._mapId = 0;
    this._tilesetId = 0;
    this._events = [];
    this._commonEvents = [];
    this._vehicles = [];
    this._displayX = 0;
    this._displayY = 0;
    this._nameDisplay = true;
    this._scrollDirection = 2;
    this._scrollRest = 0;
    this._scrollSpeed = 4;
    this._parallaxName = "";
    this._parallaxZero = false;
    this._parallaxLoopX = false;
    this._parallaxLoopY = false;
    this._parallaxSx = 0;
    this._parallaxSy = 0;
    this._parallaxX = 0;
    this._parallaxY = 0;
    this._battleback1Name = null;
    this._battleback2Name = null;
    this.createVehicles();
};

/**
 * Sets up the map for the given map id
 *
 * @param {number} mapId - The map id to setup
 * @throws Error if map data object is not available
 */
Game_Map.prototype.setup = function(mapId) {
    if (!$dataMap) {
        throw new Error("The map data is not available");
    }
    this._mapId = mapId;
    this._tilesetId = $dataMap.tilesetId;
    this._displayX = 0;
    this._displayY = 0;
    this.refereshVehicles();
    this.setupEvents();
    this.setupScroll();
    this.setupParallax();
    this.setupBattleback();
    this._needsRefresh = false;
};

/**
 * Check if events are running
 *
 * @return {boolean} True if event is running
 */
Game_Map.prototype.isEventRunning = function() {
    return this._interpreter.isRunning() || this.isAnyEventStarting();
};

/**
 * Get the width of a map tile in pixels
 *
 * @return {number} The width of one tile
 */
Game_Map.prototype.tileWidth = function() {
    if ("tileSize" in $dataSystem) {
        return $dataSystem.tileSize;
    } else {
        return 48;
    }
};

/**
 * Get the height of a map tile in pixels
 *
 * @return {number} The height of one tile
 */
Game_Map.prototype.tileHeight = function() {
    return this.tileWidth();
};

/**
 * Get the bush depth
 *
 * @return {number} The bush depth
 * @since Version 1.5.0
 */
Game_Map.prototype.bushDepth = function() {
    return this.tileHeight() / 4;
};

/**
 * Get the map id
 *
 * @return {number} The map id
 */
Game_Map.prototype.mapId = function() {
    return this._mapId;
};

/**
 * Get the tileset id
 *
 * @return {number} The tileset id
 */
Game_Map.prototype.tilesetId = function() {
    return this._tilesetId;
};

/**
 * Get the display x
 *
 * @return {number} The display x
 */
Game_Map.prototype.displayX = function() {
    return this._displayX;
};

/**
 * Get the display y
 *
 * @return {number} The display y
 */
Game_Map.prototype.displayY = function() {
    return this._displayY;
};

/**
 * Get the parallax image file name
 *
 * @return {string} The parallax name
 */
Game_Map.prototype.parallaxName = function() {
    return this._parallaxName;
};

/**
 * Get the battleback 1 image file name
 *
 * @return {string} The battleback 1 name
 */
Game_Map.prototype.battleback1Name = function() {
    return this._battleback1Name;
};

/**
 * Get the battleback 2 image file name
 *
 * @return {string} The battleback 2 name
 */
Game_Map.prototype.battleback2Name = function() {
    return this._battleback2Name;
};

/**
 * Request a map refresh
 */
Game_Map.prototype.requestRefresh = function() {
    this._needsRefresh = true;
};

/**
 * Check if the map has an enabled display name
 *
 * @return {boolean} True if name display is enabled
 */
Game_Map.prototype.isNameDisplayEnabled = function() {
    return this._nameDisplay;
};

/**
 * Disables name display
 */
Game_Map.prototype.disableNameDisplay = function() {
    this._nameDisplay = false;
};

/**
 * Enables name display
 */
Game_Map.prototype.enableNameDisplay = function() {
    this._nameDisplay = true;
};

/**
 * Creates the vehicles
 */
Game_Map.prototype.createVehicles = function() {
    this._vehicles = [];
    this._vehicles[0] = new Game_Vehicle("boat");
    this._vehicles[1] = new Game_Vehicle("ship");
    this._vehicles[2] = new Game_Vehicle("airship");
};

/**
 * Refreshes the vehicles
 */
Game_Map.prototype.refereshVehicles = function() {
    for (const vehicle of this._vehicles) {
        vehicle.refresh();
    }
};

/**
 * Get the vehicle array
 *
 * @return {Game_Vehicle[]} Array of game vehicles
 */
Game_Map.prototype.vehicles = function() {
    return this._vehicles;
};


/**
 * Get a vehicle by type
 *
 * @param {string|number} type - Either string or number type of the vehicle (0/1/2 or boat/ship/airship)
 * @return {Game_Vehicle|null} The game vehicle object, or null if invalid type
 */
Game_Map.prototype.vehicle = function(type) {
    if (type === 0 || type === "boat") {
        return this.boat();
    } else if (type === 1 || type === "ship") {
        return this.ship();
    } else if (type === 2 || type === "airship") {
        return this.airship();
    } else {
        return null;
    }
};

/**
 * Get the boat vehicle
 *
 * @return {Game_Vehicle} The boat object
 */
Game_Map.prototype.boat = function() {
    return this._vehicles[0];
};

/**
 * Get the ship vehicle
 *
 * @return {Game_Vehicle} The ship object
 */
Game_Map.prototype.ship = function() {
    return this._vehicles[1];
};

/**
 * Get the airship vehicle
 *
 * @return {Game_Vehicle} The airship object
 */
Game_Map.prototype.airship = function() {
    return this._vehicles[2];
};

/**
 * Set up events on the map
 */
Game_Map.prototype.setupEvents = function() {
    this._events = [];
    this._commonEvents = [];
    for (const event of $dataMap.events.filter(event => !!event)) {
        this._events[event.id] = new Game_Event(this._mapId, event.id);
    }
    for (const commonEvent of this.parallelCommonEvents()) {
        this._commonEvents.push(new Game_CommonEvent(commonEvent.id));
    }
    this.refreshTileEvents();
};

/**
 * Get the events
 *
 * @return {Game_Event[]} The array of map events
 */
Game_Map.prototype.events = function() {
    return this._events.filter(event => !!event);
};

/**
 * Get an event by id
 *
 * @param {number} eventId - The id of the event to get
 * @return {Game_Event} The event object
 */
Game_Map.prototype.event = function(eventId) {
    return this._events[eventId];
};

/**
 * Erase an event by id
 *
 * @param {number} eventId - The id of the event to erase
 */
Game_Map.prototype.eraseEvent = function(eventId) {
    this._events[eventId].erase();
};

/**
 * Get autorun common events
 *
 * @return {Array} Array of autorunning common events
 */
Game_Map.prototype.autorunCommonEvents = function() {
    return $dataCommonEvents.filter(
        commonEvent => commonEvent && commonEvent.trigger === 1
    );
};

/**
 * Get parallel common events
 *
 * @return {Array} Array of parallel common events
 */
Game_Map.prototype.parallelCommonEvents = function() {
    return $dataCommonEvents.filter(
        commonEvent => commonEvent && commonEvent.trigger === 2
    );
};

/**
 * Set up the map scroll
 */
Game_Map.prototype.setupScroll = function() {
    this._scrollDirection = 2;
    this._scrollRest = 0;
    this._scrollSpeed = 4;
};

/**
 * Set up the map parallax
 */
Game_Map.prototype.setupParallax = function() {
    this._parallaxName = $dataMap.parallaxName || "";
    this._parallaxZero = ImageManager.isZeroParallax(this._parallaxName);
    this._parallaxLoopX = $dataMap.parallaxLoopX;
    this._parallaxLoopY = $dataMap.parallaxLoopY;
    this._parallaxSx = $dataMap.parallaxSx;
    this._parallaxSy = $dataMap.parallaxSy;
    this._parallaxX = 0;
    this._parallaxY = 0;
};

/**
 * Set up the map battlebacks
 */
Game_Map.prototype.setupBattleback = function() {
    if ($dataMap.specifyBattleback) {
        this._battleback1Name = $dataMap.battleback1Name;
        this._battleback2Name = $dataMap.battleback2Name;
    } else {
        this._battleback1Name = null;
        this._battleback2Name = null;
    }
};

/**
 * Set up the display position
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 */
Game_Map.prototype.setDisplayPos = function(x, y) {
    if (this.isLoopHorizontal()) {
        this._displayX = x.mod(this.width());
        this._parallaxX = x;
    } else {
        const endX = this.width() - this.screenTileX();
        this._displayX = endX < 0 ? endX / 2 : x.clamp(0, endX);
        this._parallaxX = this._displayX;
    }
    if (this.isLoopVertical()) {
        this._displayY = y.mod(this.height());
        this._parallaxY = y;
    } else {
        const endY = this.height() - this.screenTileY();
        this._displayY = endY < 0 ? endY / 2 : y.clamp(0, endY);
        this._parallaxY = this._displayY;
    }
};

/**
 * Get the parallax origin x
 *
 * @return {number} The parallax origin x
 */
Game_Map.prototype.parallaxOx = function() {
    if (this._parallaxZero) {
        return this._parallaxX * this.tileWidth();
    } else if (this._parallaxLoopX) {
        return (this._parallaxX * this.tileWidth()) / 2;
    } else {
        return 0;
    }
};

/**
 * Get the parallax origin y
 *
 * @return {number} The parallax origin y
 */
Game_Map.prototype.parallaxOy = function() {
    if (this._parallaxZero) {
        return this._parallaxY * this.tileHeight();
    } else if (this._parallaxLoopY) {
        return (this._parallaxY * this.tileHeight()) / 2;
    } else {
        return 0;
    }
};

/**
 * Get the tileset object
 *
 * @return {Object} The tileset object
 */
Game_Map.prototype.tileset = function() {
    return $dataTilesets[this._tilesetId];
};

/**
 * Get the tileset flags
 *
 * @return {Array} The tileset's flags
 */
Game_Map.prototype.tilesetFlags = function() {
    const tileset = this.tileset();
    if (tileset) {
        return tileset.flags;
    } else {
        return [];
    }
};

/**
 * Get the map display name
 *
 * @return {string} The display name
 */
Game_Map.prototype.displayName = function() {
    return $dataMap.displayName;
};

/**
 * Get the map width
 *
 * @return {number} The width
 */
Game_Map.prototype.width = function() {
    return $dataMap.width;
};

/**
 * Get the map height
 *
 * @return {number} The height
 */
Game_Map.prototype.height = function() {
    return $dataMap.height;
};

/**
 * Get the map data
 *
 * @return {Array} The map data
 */
Game_Map.prototype.data = function() {
    return $dataMap.data;
};

/**
 * Check if the map loops horizontally
 *
 * @return {boolean} True if the map loops horizontally
 */
Game_Map.prototype.isLoopHorizontal = function() {
    return $dataMap.scrollType === 2 || $dataMap.scrollType === 3;
};

/**
 * Check if the map loops vertically
 *
 * @return {boolean} True if the map loops vertically
 */
Game_Map.prototype.isLoopVertical = function() {
    return $dataMap.scrollType === 1 || $dataMap.scrollType === 3;
};

/**
 * Check if dashing is disabled
 *
 * @return {boolean} True if dashing disabled
 */
Game_Map.prototype.isDashDisabled = function() {
    return $dataMap.disableDashing;
};

/**
 * Get the map encounter list
 *
 * @return {Array} The encounter list
 */
Game_Map.prototype.encounterList = function() {
    return $dataMap.encounterList;
};

/**
 * Get the map encounter step
 *
 * @return {number} The encounter step
 */
Game_Map.prototype.encounterStep = function() {
    return $dataMap.encounterStep;
};

/**
 * Check if the map is an overworld type
 *
 * @return {boolean} True if overworld type
 */
Game_Map.prototype.isOverworld = function() {
    return this.tileset() && this.tileset().mode === 0;
};

/**
 * Get the screen tile x
 *
 * @return {number} The screen tile x
 */
Game_Map.prototype.screenTileX = function() {
    return Math.round((Graphics.width / this.tileWidth()) * 16) / 16;
};

/**
 * Get the screen tile y
 *
 * @return {number} The screen tile y
 */
Game_Map.prototype.screenTileY = function() {
    return Math.round((Graphics.height / this.tileHeight()) * 16) / 16;
};

/**
 * Get the adjusted x value
 *
 * @param {number} x - The x to adjust
 * @return {number} The adjusted x value
 */
Game_Map.prototype.adjustX = function(x) {
    if (
        this.isLoopHorizontal() &&
        x < this._displayX - (this.width() - this.screenTileX()) / 2
    ) {
        return x - this._displayX + $dataMap.width;
    } else {
        return x - this._displayX;
    }
};

/**
 * Get the adjusted y value
 *
 * @param {number} y - The y to adjust
 * @return {number} The adjusted y value
 */
Game_Map.prototype.adjustY = function(y) {
    if (
        this.isLoopVertical() &&
        y < this._displayY - (this.height() - this.screenTileY()) / 2
    ) {
        return y - this._displayY + $dataMap.height;
    } else {
        return y - this._displayY;
    }
};

/**
 * Get the rounded x value
 *
 * @param {number} x - The x to round
 * @return {number} The rounded x value
 */
Game_Map.prototype.roundX = function(x) {
    return this.isLoopHorizontal() ? x.mod(this.width()) : x;
};

/**
 * Get the rounded y value
 *
 * @param {number} y - The y to round
 * @return {number} The rounded y value
 */
Game_Map.prototype.roundY = function(y) {
    return this.isLoopVertical() ? y.mod(this.height()) : y;
};

/**
 * Get the x value with direction
 *
 * @param {number} x - The original x value
 * @param {number} d - The direction
 * @return {number} The x value after direction accounted for
 */
Game_Map.prototype.xWithDirection = function(x, d) {
    return x + (d === 6 ? 1 : d === 4 ? -1 : 0);
};

/**
 * Get the y value with direction
 *
 * @param {number} y - The original y value
 * @param {number} d - The direction
 * @return {number} The y value after direction accounted for
 */
Game_Map.prototype.yWithDirection = function(y, d) {
    return y + (d === 2 ? 1 : d === 8 ? -1 : 0);
};

/**
 * Get the rounded x value with direction
 *
 * @param {number} x - The original x value
 * @param {number} d - The direction
 * @return {number} The x value after direction accounted for
 */
Game_Map.prototype.roundXWithDirection = function(x, d) {
    return this.roundX(x + (d === 6 ? 1 : d === 4 ? -1 : 0));
};

/**
 * Get the rounded y value with direction
 *
 * @param {number} y - The original y value
 * @param {number} d - The direction
 * @return {number} The y value after direction accounted for
 */
Game_Map.prototype.roundYWithDirection = function(y, d) {
    return this.roundY(y + (d === 2 ? 1 : d === 8 ? -1 : 0));
};

/**
 * Get the delta between two x values
 *
 * @param {number} x1 - The first x value
 * @param {number} x2 - The second x value
 * @return {number} The delta between the x values
 */
Game_Map.prototype.deltaX = function(x1, x2) {
    let result = x1 - x2;
    if (this.isLoopHorizontal() && Math.abs(result) > this.width() / 2) {
        if (result < 0) {
            result += this.width();
        } else {
            result -= this.width();
        }
    }
    return result;
};

/**
 * Get the delta between two y values
 *
 * @param {number} y1 - The first y value
 * @param {number} y2 - The second y value
 * @return {number} The delta between the y values
 */
Game_Map.prototype.deltaY = function(y1, y2) {
    let result = y1 - y2;
    if (this.isLoopVertical() && Math.abs(result) > this.height() / 2) {
        if (result < 0) {
            result += this.height();
        } else {
            result -= this.height();
        }
    }
    return result;
};

/**
 * Get the distance between two sets of x/y coordinates
 *
 * @param {number} x1 - The first x value
 * @param {number} y1 - The first y value
 * @param {number} x2 - The second x value
 * @param {number} y2 - The second y value
 * @return {number} The distance
 */
Game_Map.prototype.distance = function(x1, y1, x2, y2) {
    return Math.abs(this.deltaX(x1, x2)) + Math.abs(this.deltaY(y1, y2));
};

/**
 * Get map x from canvas x
 *
 * @param {number} x - The x value to convert
 * @return {number} The map x
 */
Game_Map.prototype.canvasToMapX = function(x) {
    const tileWidth = this.tileWidth();
    const originX = this._displayX * tileWidth;
    const mapX = Math.floor((originX + x) / tileWidth);
    return this.roundX(mapX);
};

/**
 * Get map y from canvas y
 *
 * @param {number} y - The y value to convert
 * @return {number} The map y
 */
Game_Map.prototype.canvasToMapY = function(y) {
    const tileHeight = this.tileHeight();
    const originY = this._displayY * tileHeight;
    const mapY = Math.floor((originY + y) / tileHeight);
    return this.roundY(mapY);
};

/**
 * Autoplays map audio
 */
Game_Map.prototype.autoplay = function() {
    if ($dataMap.autoplayBgm) {
        if ($gamePlayer.isInVehicle()) {
            $gameSystem.saveWalkingBgm2();
        } else {
            AudioManager.playBgm($dataMap.bgm);
        }
    }
    if ($dataMap.autoplayBgs) {
        AudioManager.playBgs($dataMap.bgs);
    }
};

/**
 * Refresh the map only if needed
 */
Game_Map.prototype.refreshIfNeeded = function() {
    if (this._needsRefresh) {
        this.refresh();
    }
};

/**
 * Refresh the map
 */
Game_Map.prototype.refresh = function() {
    for (const event of this.events()) {
        event.refresh();
    }
    for (const commonEvent of this._commonEvents) {
        commonEvent.refresh();
    }
    this.refreshTileEvents();
    this._needsRefresh = false;
};

/**
 * Refresh tile events
 */
Game_Map.prototype.refreshTileEvents = function() {
    this._tileEvents = this.events().filter(event => event.isTile());
};

/**
 * Get events at the given coordinates
 *
 * @param {number} x - The x coordinate to check for events
 * @param {number} y - The y coordinate to check for events
 * @return {Game_Event[]} The events at the given coordinates
 */
Game_Map.prototype.eventsXy = function(x, y) {
    return this.events().filter(event => event.pos(x, y));
};

/**
 * Get no-through events at the given coordinates
 *
 * @param {number} x - The x coordinate to check for events
 * @param {number} y - The y coordinate to check for events
 * @return {Game_Event[]} The NT events at the given coordinates
 */
Game_Map.prototype.eventsXyNt = function(x, y) {
    return this.events().filter(event => event.posNt(x, y));
};

/**
 * Get tile events at the given coordinates
 *
 * @param {number} x - The x coordinate to check for events
 * @param {number} y - The y coordinate to check for events
 * @return {Game_Event[]} The tile events at the given coordinates
 */
Game_Map.prototype.tileEventsXy = function(x, y) {
    return this._tileEvents.filter(event => event.posNt(x, y));
};

/**
 * Get the event id at the given coordinates
 *
 * @param {number} x - The x coordinate to check for events
 * @param {number} y - The y coordinate to check for events
 * @return {number} The event id
 */
Game_Map.prototype.eventIdXy = function(x, y) {
    const list = this.eventsXy(x, y);
    return list.length === 0 ? 0 : list[0].eventId();
};

/**
 * Scrolls the map down
 *
 * @param {number} distance - The distance to scroll
 */
Game_Map.prototype.scrollDown = function(distance) {
    if (this.isLoopVertical()) {
        this._displayY += distance;
        this._displayY %= $dataMap.height;
        if (this._parallaxLoopY) {
            this._parallaxY += distance;
        }
    } else if (this.height() >= this.screenTileY()) {
        const lastY = this._displayY;
        this._displayY = Math.min(
            this._displayY + distance,
            this.height() - this.screenTileY()
        );
        this._parallaxY += this._displayY - lastY;
    }
};

/**
 * Scrolls the map left
 *
 * @param {number} distance - The distance to scroll
 */
Game_Map.prototype.scrollLeft = function(distance) {
    if (this.isLoopHorizontal()) {
        this._displayX += $dataMap.width - distance;
        this._displayX %= $dataMap.width;
        if (this._parallaxLoopX) {
            this._parallaxX -= distance;
        }
    } else if (this.width() >= this.screenTileX()) {
        const lastX = this._displayX;
        this._displayX = Math.max(this._displayX - distance, 0);
        this._parallaxX += this._displayX - lastX;
    }
};

/**
 * Scrolls the map right
 *
 * @param {number} distance - The distance to scroll
 */
Game_Map.prototype.scrollRight = function(distance) {
    if (this.isLoopHorizontal()) {
        this._displayX += distance;
        this._displayX %= $dataMap.width;
        if (this._parallaxLoopX) {
            this._parallaxX += distance;
        }
    } else if (this.width() >= this.screenTileX()) {
        const lastX = this._displayX;
        this._displayX = Math.min(
            this._displayX + distance,
            this.width() - this.screenTileX()
        );
        this._parallaxX += this._displayX - lastX;
    }
};

/**
 * Scrolls the map up
 *
 * @param {number} distance - The distance to scroll
 */
Game_Map.prototype.scrollUp = function(distance) {
    if (this.isLoopVertical()) {
        this._displayY += $dataMap.height - distance;
        this._displayY %= $dataMap.height;
        if (this._parallaxLoopY) {
            this._parallaxY -= distance;
        }
    } else if (this.height() >= this.screenTileY()) {
        const lastY = this._displayY;
        this._displayY = Math.max(this._displayY - distance, 0);
        this._parallaxY += this._displayY - lastY;
    }
};

/**
 * Check if the x/y coordinates given are valid
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @return {boolean} True if the coordinates are within the map bounds
 */
Game_Map.prototype.isValid = function(x, y) {
    return x >= 0 && x < this.width() && y >= 0 && y < this.height();
};

/**
 * Check the passage of the tile at the given coordinates
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @param {number} bit - The bit to perform bitwise AND operation on with the tile
 * @return {boolean} True if passage allowed
 */
Game_Map.prototype.checkPassage = function(x, y, bit) {
    const flags = this.tilesetFlags();
    const tiles = this.allTiles(x, y);
    for (const tile of tiles) {
        const flag = flags[tile];
        if ((flag & 0x10) !== 0) {
            // [*] No effect on passage
            continue;
        }
        if ((flag & bit) === 0) {
            // [o] Passable
            return true;
        }
        if ((flag & bit) === bit) {
            // [x] Impassable
            return false;
        }
    }
    return false;
};

/**
 * Get the tile id by coordinate
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @param {number} z - The z index of the tile
 * @return {number} The tile id
 */
Game_Map.prototype.tileId = function(x, y, z) {
    const width = $dataMap.width;
    const height = $dataMap.height;
    return $dataMap.data[(z * height + y) * width + x] || 0;
};

/**
 * Get the layered tiles at the given coordinates
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 * @return {Array} The tiles at the coordinates
 */
Game_Map.prototype.layeredTiles = function(x, y) {
    const tiles = [];
    for (let i = 0; i < 4; i++) {
        tiles.push(this.tileId(x, y, 3 - i));
    }
    return tiles;
};

/**
 * Get the tiles at the given coordinates
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 * @return {Array} The tiles at the coordinates
 */
Game_Map.prototype.allTiles = function(x, y) {
    const tiles = this.tileEventsXy(x, y).map(event => event.tileId());
    return tiles.concat(this.layeredTiles(x, y));
};

/**
 * Get the autotile type at the given coordinates
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 * @param {number} z - The z index of the tile
 * @return {number} The autotile type
 */
Game_Map.prototype.autotileType = function(x, y, z) {
    const tileId = this.tileId(x, y, z);
    return tileId >= 2048 ? Math.floor((tileId - 2048) / 48) : -1;
};

/**
 * Check if the map is passable
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 * @param {number} d - The direction to check
 * @return {boolean} True if passable
 */
Game_Map.prototype.isPassable = function(x, y, d) {
    return this.checkPassage(x, y, (1 << (d / 2 - 1)) & 0x0f);
};

/**
 * Check if the map is passable for the boat
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 * @return {boolean} True if passable
 */
Game_Map.prototype.isBoatPassable = function(x, y) {
    return this.checkPassage(x, y, 0x0200);
};

/**
 * Check if the map is passable for the ship
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 * @return {boolean} True if passable
 */
Game_Map.prototype.isShipPassable = function(x, y) {
    return this.checkPassage(x, y, 0x0400);
};

/**
 * Check if the airship can land at the given coordinates
 *
 * @param {number} x - The x coordinate
 * @param {number} y - The y coordinate
 * @return {boolean} True if landing is ok
 */
Game_Map.prototype.isAirshipLandOk = function(x, y) {
    return this.checkPassage(x, y, 0x0800) && this.checkPassage(x, y, 0x0f);
};

/**
 * Check the layered tile flags
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @param {number} bit - The bit to perform bitwise AND operation on with the tile
 * @return {boolean} True if a layered tile flag exists
 */
Game_Map.prototype.checkLayeredTilesFlags = function(x, y, bit) {
    const flags = this.tilesetFlags();
    return this.layeredTiles(x, y).some(tileId => (flags[tileId] & bit) !== 0);
};

/**
 * Check if the tile is a ladder
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @return {boolean} True if the tile is a ladder
 */
Game_Map.prototype.isLadder = function(x, y) {
    return this.isValid(x, y) && this.checkLayeredTilesFlags(x, y, 0x20);
};

/**
 * Check if the tile is a bush
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @return {boolean} True if the tile is a bush
 */
Game_Map.prototype.isBush = function(x, y) {
    return this.isValid(x, y) && this.checkLayeredTilesFlags(x, y, 0x40);
};

/**
 * Check if the tile is a counter
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @return {boolean} True if the tile is a counter
 */
Game_Map.prototype.isCounter = function(x, y) {
    return this.isValid(x, y) && this.checkLayeredTilesFlags(x, y, 0x80);
};

/**
 * Check if the tile is a damage floor
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @return {boolean} True if the tile is a damage floor
 */
Game_Map.prototype.isDamageFloor = function(x, y) {
    return this.isValid(x, y) && this.checkLayeredTilesFlags(x, y, 0x100);
};

/**
 * Get the terrain tag of a tile
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @return {number} The terrain tag of the tile
 */
Game_Map.prototype.terrainTag = function(x, y) {
    if (this.isValid(x, y)) {
        const flags = this.tilesetFlags();
        const tiles = this.layeredTiles(x, y);
        for (const tile of tiles) {
            const tag = flags[tile] >> 12;
            if (tag > 0) {
                return tag;
            }
        }
    }
    return 0;
};

/**
 * Get the region id of a tile
 *
 * @param {number} x - The x coordinate to check
 * @param {number} y - The y coordinate to check
 * @return {number} The region id of the tile
 */
Game_Map.prototype.regionId = function(x, y) {
    return this.isValid(x, y) ? this.tileId(x, y, 5) : 0;
};

/**
 * Start a map scroll
 *
 * @param {number} direction - The direction of the scroll
 * @param {number} distance - The distance of the scroll
 * @param {number} speed - The speed of the scroll
 */
Game_Map.prototype.startScroll = function(direction, distance, speed) {
    this._scrollDirection = direction;
    this._scrollRest = distance;
    this._scrollSpeed = speed;
};

/**
 * Check if the map is scrolling
 *
 * @return {boolean} True if scrolling
 */
Game_Map.prototype.isScrolling = function() {
    return this._scrollRest > 0;
};

/**
 * Update the map
 *
 * @param {boolean} sceneActive - If the scene is active
 */
Game_Map.prototype.update = function(sceneActive) {
    this.refreshIfNeeded();
    if (sceneActive) {
        this.updateInterpreter();
    }
    this.updateScroll();
    this.updateEvents();
    this.updateVehicles();
    this.updateParallax();
};

/**
 * Update the map scroll
 */
Game_Map.prototype.updateScroll = function() {
    if (this.isScrolling()) {
        const lastX = this._displayX;
        const lastY = this._displayY;
        this.doScroll(this._scrollDirection, this.scrollDistance());
        if (this._displayX === lastX && this._displayY === lastY) {
            this._scrollRest = 0;
        } else {
            this._scrollRest -= this.scrollDistance();
        }
    }
};

/**
 * Get the distance to scroll
 *
 * @return {number} The distance to scroll
 */
Game_Map.prototype.scrollDistance = function() {
    return Math.pow(2, this._scrollSpeed) / 256;
};

/**
 * Perform the scroll
 *
 * @param {number} direction - The direction to scroll
 * @param {number} distance - The distance to scroll
 */
Game_Map.prototype.doScroll = function(direction, distance) {
    switch (direction) {
        case 2:
            this.scrollDown(distance);
            break;
        case 4:
            this.scrollLeft(distance);
            break;
        case 6:
            this.scrollRight(distance);
            break;
        case 8:
            this.scrollUp(distance);
            break;
    }
};

/**
 * Update events
 */
Game_Map.prototype.updateEvents = function() {
    for (const event of this.events()) {
        event.update();
    }
    for (const commonEvent of this._commonEvents) {
        commonEvent.update();
    }
};

/**
 * Update vehicles
 */
Game_Map.prototype.updateVehicles = function() {
    for (const vehicle of this._vehicles) {
        vehicle.update();
    }
};

/**
 * Update the parallax
 */
Game_Map.prototype.updateParallax = function() {
    if (this._parallaxLoopX) {
        this._parallaxX += this._parallaxSx / this.tileWidth() / 2;
    }
    if (this._parallaxLoopY) {
        this._parallaxY += this._parallaxSy / this.tileHeight() / 2;
    }
};

/**
 * Changes the tileset by id
 *
 * @param {number} tilesetId - The new tileset id
 */
Game_Map.prototype.changeTileset = function(tilesetId) {
    this._tilesetId = tilesetId;
    this.refresh();
};

/**
 * Changes the battleback images
 *
 * @param {string} battleback1Name - The filename of the battle back 1 image
 * @param {string} battleback2Name - The filename of the battle back 2 image
 */
Game_Map.prototype.changeBattleback = function(
    battleback1Name,
    battleback2Name
) {
    this._battleback1Name = battleback1Name;
    this._battleback2Name = battleback2Name;
};

/**
 * Changes the parallax image
 *
 * @param {string} name - The filename of the parallax image
 * @param {boolean} loopX - If the parallax loops horizontally
 * @param {boolean} loopY - If the parallax loops vertically
 * @param {number} sx - The horziontal scroll of the parallax
 * @param {number} sy - The vertical scroll of the parallax
 */
Game_Map.prototype.changeParallax = function(name, loopX, loopY, sx, sy) {
    this._parallaxName = name;
    this._parallaxZero = ImageManager.isZeroParallax(this._parallaxName);
    if (this._parallaxLoopX && !loopX) {
        this._parallaxX = 0;
    }
    if (this._parallaxLoopY && !loopY) {
        this._parallaxY = 0;
    }
    this._parallaxLoopX = loopX;
    this._parallaxLoopY = loopY;
    this._parallaxSx = sx;
    this._parallaxSy = sy;
};

/**
 * Update the interpreter
 */
Game_Map.prototype.updateInterpreter = function() {
    for (;;) {
        this._interpreter.update();
        if (this._interpreter.isRunning()) {
            return;
        }
        if (this._interpreter.eventId() > 0) {
            this.unlockEvent(this._interpreter.eventId());
            this._interpreter.clear();
        }
        if (!this.setupStartingEvent()) {
            return;
        }
    }
};

/**
 * Unlock an event by id
 *
 * @param {number} eventId - The id of the event to unlock
 */
Game_Map.prototype.unlockEvent = function(eventId) {
    if (this._events[eventId]) {
        this._events[eventId].unlock();
    }
};

/**
 * Sets up a starting event
 *
 * @return {boolean} True if event set up
 */
Game_Map.prototype.setupStartingEvent = function() {
    this.refreshIfNeeded();
    if (this._interpreter.setupReservedCommonEvent()) {
        return true;
    }
    if (this.setupTestEvent()) {
        return true;
    }
    if (this.setupStartingMapEvent()) {
        return true;
    }
    if (this.setupAutorunCommonEvent()) {
        return true;
    }
    return false;
};

/**
 * Sets up a test event
 *
 * @return {boolean} True if event set up
 */
Game_Map.prototype.setupTestEvent = function() {
    if (window.$testEvent) {
        this._interpreter.setup($testEvent, 0);
        $testEvent = null;
        return true;
    }
    return false;
};

/**
 * Sets up a starting map event
 *
 * @return {boolean} True if event set up
 */
Game_Map.prototype.setupStartingMapEvent = function() {
    for (const event of this.events()) {
        if (event.isStarting()) {
            event.clearStartingFlag();
            this._interpreter.setup(event.list(), event.eventId());
            return true;
        }
    }
    return false;
};

/**
 * Sets up an autorun common event
 *
 * @return {boolean} True if event set up
 */
Game_Map.prototype.setupAutorunCommonEvent = function() {
    for (const commonEvent of this.autorunCommonEvents()) {
        if ($gameSwitches.value(commonEvent.switchId)) {
            this._interpreter.setup(commonEvent.list);
            return true;
        }
    }
    return false;
};

/**
 * Check if any events are starting
 *
 * @return {boolean} True if any event is starting
 */
Game_Map.prototype.isAnyEventStarting = function() {
    return this.events().some(event => event.isStarting());
};