"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.spawnVoidPortal = exports.spawnGridEntityWithVariant = exports.spawnGridEntity = exports.spawnGiantPoop = exports.setGridEntityType = exports.setGridEntityInvisible = exports.removeGridEntity = exports.removeGridEntities = exports.removeEntitiesSpawnedFromGridEntity = exports.removeAllMatchingGridEntities = exports.removeAllGridEntitiesExcept = exports.isPostBossVoidPortal = exports.isPoopGridEntityXMLType = exports.isGridIndexAdjacentToDoor = exports.isGridEntityXMLType = exports.isGridEntityBroken = exports.isGridEntityBreakableByExplosion = exports.getTopLeftWallGridIndex = exports.getTopLeftWall = exports.getSurroundingGridIndexes = exports.getSurroundingGridEntities = exports.getRockPNGPath = exports.getMatchingGridEntities = exports.getGridEntityIDFromConstituents = exports.getGridEntityID = exports.getGridEntityCollisionPoints = exports.getGridEntityANM2Path = exports.getGridEntitiesMap = exports.getGridEntitiesInRadius = exports.getGridEntitiesExcept = exports.getGridEntities = exports.getConstituentsFromGridEntityID = exports.getCollidingEntitiesWithGridEntity = exports.getAllGridIndexes = exports.doesGridEntityExist = exports.convertXMLGridEntityType = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedEnumValues_1 = require("../cachedEnumValues");
const cachedClasses_1 = require("../core/cachedClasses");
const constants_1 = require("../core/constants");
const gridEntityTypeToBrokenStateMap_1 = require("../maps/gridEntityTypeToBrokenStateMap");
const gridEntityXMLMap_1 = require("../maps/gridEntityXMLMap");
const roomShapeToTopLeftWallGridIndexMap_1 = require("../maps/roomShapeToTopLeftWallGridIndexMap");
const gridEntityTypeToANM2Name_1 = require("../objects/gridEntityTypeToANM2Name");
const poopGridEntityXMLTypesSet_1 = require("../sets/poopGridEntityXMLTypesSet");
const ReadonlySet_1 = require("../types/ReadonlySet");
const entities_1 = require("./entities");
const entitiesSpecific_1 = require("./entitiesSpecific");
const math_1 = require("./math");
const rooms_1 = require("./rooms");
const types_1 = require("./types");
const utils_1 = require("./utils");
const vector_1 = require("./vector");
/**
 * For some specific grid entities, the variant defined in the XML is what is used by the actual
 * game (which is not the case for e.g. poops).
 */
const GRID_ENTITY_TYPES_THAT_KEEP_GRID_ENTITY_XML_VARIANT = new ReadonlySet_1.ReadonlySet([
    isaac_typescript_definitions_1.GridEntityType.SPIKES_ON_OFF,
    isaac_typescript_definitions_1.GridEntityType.PRESSURE_PLATE,
    isaac_typescript_definitions_1.GridEntityType.TELEPORTER, // 23
]);
const BREAKABLE_GRID_ENTITY_TYPES_BY_EXPLOSIONS = new ReadonlySet_1.ReadonlySet([
    isaac_typescript_definitions_1.GridEntityType.ROCK,
    isaac_typescript_definitions_1.GridEntityType.ROCK_TINTED,
    isaac_typescript_definitions_1.GridEntityType.ROCK_BOMB,
    isaac_typescript_definitions_1.GridEntityType.ROCK_ALT,
    isaac_typescript_definitions_1.GridEntityType.SPIDER_WEB,
    isaac_typescript_definitions_1.GridEntityType.TNT,
    // GridEntityType.FIREPLACE (13) does not count since it is turned into a non-grid entity upon
    // spawning.
    isaac_typescript_definitions_1.GridEntityType.POOP,
    isaac_typescript_definitions_1.GridEntityType.ROCK_SUPER_SPECIAL,
    isaac_typescript_definitions_1.GridEntityType.ROCK_SPIKED,
    isaac_typescript_definitions_1.GridEntityType.ROCK_ALT_2,
    isaac_typescript_definitions_1.GridEntityType.ROCK_GOLD, // 27
]);
const BREAKABLE_GRID_ENTITY_TYPES_VARIANTS_BY_EXPLOSIONS = new ReadonlySet_1.ReadonlySet([`${isaac_typescript_definitions_1.GridEntityType.STATUE}.${isaac_typescript_definitions_1.StatueVariant.ANGEL}`]);
const GRID_ENTITY_XML_TYPES_SET = new ReadonlySet_1.ReadonlySet(cachedEnumValues_1.GRID_ENTITY_XML_TYPE_VALUES);
/**
 * Helper function to convert the grid entity type found in a room XML file to the corresponding
 * grid entity type and variant normally used by the game. For example, `GridEntityXMLType.ROCK` is
 * 1000 (in a room XML file), but `GridEntityType.ROCK` is equal to 2 (in-game).
 */
function convertXMLGridEntityType(gridEntityXMLType, gridEntityXMLVariant) {
    const gridEntityArray = gridEntityXMLMap_1.GRID_ENTITY_XML_MAP.get(gridEntityXMLType);
    (0, utils_1.assertDefined)(gridEntityArray, `Failed to find an entry in the grid entity map for XML entity type: ${gridEntityXMLType}`);
    const gridEntityType = gridEntityArray[0];
    const variant = GRID_ENTITY_TYPES_THAT_KEEP_GRID_ENTITY_XML_VARIANT.has(gridEntityType)
        ? gridEntityXMLVariant
        : gridEntityArray[1];
    return [gridEntityType, variant];
}
exports.convertXMLGridEntityType = convertXMLGridEntityType;
/**
 * Helper function to check if one or more of a specific kind of grid entity is present in the
 * current room.
 *
 * @param gridEntityType The grid entity type to match.
 * @param variant Optional. Default is -1, which matches every variant.
 */
function doesGridEntityExist(gridEntityType, variant = -1) {
    const room = cachedClasses_1.game.GetRoom();
    const gridIndexes = getAllGridIndexes();
    return gridIndexes.some((gridIndex) => {
        const gridEntity = room.GetGridEntity(gridIndex);
        if (gridEntity === undefined) {
            return false;
        }
        const thisGridEntityType = gridEntity.GetType();
        const thisVariant = gridEntity.GetVariant();
        return (gridEntityType === thisGridEntityType &&
            (variant === -1 || variant === thisVariant));
    });
}
exports.doesGridEntityExist = doesGridEntityExist;
/**
 * Helper function to get every legal grid index for the current room.
 *
 * Under the hood, this uses the `Room.GetGridSize` method.
 */
function getAllGridIndexes() {
    const room = cachedClasses_1.game.GetRoom();
    const gridSize = room.GetGridSize();
    return (0, utils_1.eRange)(gridSize);
}
exports.getAllGridIndexes = getAllGridIndexes;
/**
 * Gets the entities that have a hitbox that overlaps with any part of the square that the grid
 * entity is on.
 *
 * This function is useful because the vanilla collision callbacks do not work with grid entities.
 * This is used by `POST_GRID_ENTITY_COLLISION` custom callback.
 *
 * Note that this function will not work properly in the `POST_NEW_ROOM` callback since entities do
 * not have collision yet in that callback.
 */
function getCollidingEntitiesWithGridEntity(gridEntity) {
    const { topLeft, bottomRight } = getGridEntityCollisionPoints(gridEntity);
    const closeEntities = Isaac.FindInRadius(gridEntity.Position, constants_1.DISTANCE_OF_GRID_TILE * 2);
    return closeEntities.filter((entity) => entity.CollidesWithGrid() &&
        (0, math_1.isCircleIntersectingRectangle)(entity.Position, 
        // We arbitrarily add 0.1 to account for entities that are already pushed back by the time
        // the `POST_UPDATE` callback fires.
        entity.Size + 0.1, topLeft, bottomRight));
}
exports.getCollidingEntitiesWithGridEntity = getCollidingEntitiesWithGridEntity;
/** Helper function to get the grid entity type and variant from a `GridEntityID`. */
function getConstituentsFromGridEntityID(gridEntityID) {
    const parts = gridEntityID.split(".");
    if (parts.length !== 2) {
        error(`Failed to get the constituents from a grid entity ID: ${gridEntityID}`);
    }
    const [gridEntityTypeString, variantString] = parts;
    (0, utils_1.assertDefined)(gridEntityTypeString, `Failed to get the first constituent from a grid entity ID: ${gridEntityID}`);
    (0, utils_1.assertDefined)(variantString, `Failed to get the second constituent from a grid entity ID: ${gridEntityID}`);
    const gridEntityType = (0, types_1.parseIntSafe)(gridEntityTypeString);
    (0, utils_1.assertDefined)(gridEntityType, `Failed to convert the grid entity type to a number: ${gridEntityTypeString}`);
    const variant = (0, types_1.parseIntSafe)(variantString);
    (0, utils_1.assertDefined)(variant, `Failed to convert the grid entity variant to an integer: ${variantString}`);
    return [gridEntityType, variant];
}
exports.getConstituentsFromGridEntityID = getConstituentsFromGridEntityID;
/**
 * Helper function to get every grid entity in the current room.
 *
 * Use this function with no arguments to get every grid entity, or specify a variadic amount of
 * arguments to match specific grid entity types.
 *
 * For example:
 *
 * ```ts
 * for (const gridEntity of getGridEntities()) {
 *   print(gridEntity.GetType())
 * }
 * ```
 *
 * For example:
 *
 * ```ts
 * const rocks = getGridEntities(
 *   GridEntityType.ROCK,
 *   GridEntityType.BLOCK,
 *   GridEntityType.ROCK_TINTED,
 * );
 * ```
 *
 * @allowEmptyVariadic
 */
function getGridEntities(...gridEntityTypes) {
    const gridEntities = getAllGridEntities();
    if (gridEntityTypes.length === 0) {
        return gridEntities;
    }
    const gridEntityTypesSet = new ReadonlySet_1.ReadonlySet(gridEntityTypes);
    return gridEntities.filter((gridEntity) => {
        const gridEntityType = gridEntity.GetType();
        return gridEntityTypesSet.has(gridEntityType);
    });
}
exports.getGridEntities = getGridEntities;
/**
 * Helper function to get every grid entity in the current room except for certain specific types.
 *
 * This function is variadic, meaning that you can specify as many grid entity types as you want to
 * exclude.
 */
function getGridEntitiesExcept(...gridEntityTypes) {
    const gridEntities = getAllGridEntities();
    if (gridEntityTypes.length === 0) {
        return gridEntities;
    }
    const gridEntityTypesSet = new ReadonlySet_1.ReadonlySet(gridEntityTypes);
    return gridEntities.filter((gridEntity) => {
        const gridEntityType = gridEntity.GetType();
        return !gridEntityTypesSet.has(gridEntityType);
    });
}
exports.getGridEntitiesExcept = getGridEntitiesExcept;
function getAllGridEntities() {
    const room = cachedClasses_1.game.GetRoom();
    const gridEntities = [];
    for (const gridIndex of getAllGridIndexes()) {
        const gridEntity = room.GetGridEntity(gridIndex);
        if (gridEntity !== undefined) {
            gridEntities.push(gridEntity);
        }
    }
    return gridEntities;
}
/** Helper function to get all grid entities in a given radius around a given point. */
function getGridEntitiesInRadius(targetPosition, radius) {
    radius = Math.abs(radius);
    const topLeftOffset = constants_1.VectorOne.mul(-radius);
    const mostTopLeftPosition = targetPosition.add(topLeftOffset);
    const room = cachedClasses_1.game.GetRoom();
    const diameter = radius * 2;
    const iterations = Math.ceil(diameter / constants_1.DISTANCE_OF_GRID_TILE);
    const separation = diameter / iterations;
    const gridEntities = [];
    const registeredGridIndexes = new Set();
    for (const x of (0, utils_1.iRange)(iterations)) {
        for (const y of (0, utils_1.iRange)(iterations)) {
            const position = mostTopLeftPosition.add(Vector(x * separation, y * separation));
            const gridIndex = room.GetGridIndex(position);
            const gridEntity = room.GetGridEntityFromPos(position);
            if (gridEntity === undefined || registeredGridIndexes.has(gridIndex)) {
                continue;
            }
            registeredGridIndexes.add(gridIndex);
            const { topLeft, bottomRight } = getGridEntityCollisionPoints(gridEntity);
            if ((0, math_1.isCircleIntersectingRectangle)(targetPosition, radius, topLeft, bottomRight)) {
                gridEntities.push(gridEntity);
            }
        }
    }
    return gridEntities;
}
exports.getGridEntitiesInRadius = getGridEntitiesInRadius;
/**
 * Helper function to get a map of every grid entity in the current room. The indexes of the map are
 * equal to the grid index. The values of the map are equal to the grid entities.
 *
 * Use this function with no arguments to get every grid entity, or specify a variadic amount of
 * arguments to match specific grid entity types.
 *
 * @allowEmptyVariadic
 */
function getGridEntitiesMap(...gridEntityTypes) {
    const gridEntities = getGridEntities(...gridEntityTypes);
    const gridEntityMap = new Map();
    for (const gridEntity of gridEntities) {
        const gridIndex = gridEntity.GetGridIndex();
        gridEntityMap.set(gridIndex, gridEntity);
    }
    return gridEntityMap;
}
exports.getGridEntitiesMap = getGridEntitiesMap;
/** Helper function to get the ANM2 path for a grid entity type. */
function getGridEntityANM2Path(gridEntityType) {
    const gridEntityANM2Name = getGridEntityANM2Name(gridEntityType);
    return `gfx/grid/${gridEntityANM2Name}`;
}
exports.getGridEntityANM2Path = getGridEntityANM2Path;
function getGridEntityANM2Name(gridEntityType) {
    switch (gridEntityType) {
        // 1
        case isaac_typescript_definitions_1.GridEntityType.DECORATION: {
            return getGridEntityANM2NameDecoration();
        }
        default: {
            return gridEntityTypeToANM2Name_1.GRID_ENTITY_TYPE_TO_ANM2_NAME[gridEntityType];
        }
    }
}
/**
 * Helper function to get the ANM2 path for a decoration. This depends on the current room's
 * backdrop. The values are taken from the "backdrops.xml" file.
 */
function getGridEntityANM2NameDecoration() {
    const room = cachedClasses_1.game.GetRoom();
    const backdropType = room.GetBackdropType();
    switch (backdropType) {
        // 1, 2, 3, 36, 49, 52
        case isaac_typescript_definitions_1.BackdropType.BASEMENT:
        case isaac_typescript_definitions_1.BackdropType.CELLAR:
        case isaac_typescript_definitions_1.BackdropType.BURNING_BASEMENT:
        case isaac_typescript_definitions_1.BackdropType.DOWNPOUR_ENTRANCE:
        case isaac_typescript_definitions_1.BackdropType.ISAACS_BEDROOM:
        case isaac_typescript_definitions_1.BackdropType.CLOSET: {
            return "Props_01_Basement.anm2";
        }
        // 4, 5, 6, 37
        case isaac_typescript_definitions_1.BackdropType.CAVES:
        case isaac_typescript_definitions_1.BackdropType.CATACOMBS:
        case isaac_typescript_definitions_1.BackdropType.FLOODED_CAVES:
        case isaac_typescript_definitions_1.BackdropType.MINES_ENTRANCE: {
            return "Props_03_Caves.anm2";
        }
        // 7, 8, 9, 30, 33, 38, 39, 40, 41, 42, 53, 60
        case isaac_typescript_definitions_1.BackdropType.DEPTHS:
        case isaac_typescript_definitions_1.BackdropType.NECROPOLIS:
        case isaac_typescript_definitions_1.BackdropType.DANK_DEPTHS:
        case isaac_typescript_definitions_1.BackdropType.SACRIFICE:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_ENTRANCE:
        case isaac_typescript_definitions_1.BackdropType.CORPSE_ENTRANCE:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_2:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_3:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_4:
        case isaac_typescript_definitions_1.BackdropType.CLOSET_B:
        case isaac_typescript_definitions_1.BackdropType.DARK_CLOSET: {
            return "Props_05_Depths.anm2";
        }
        // 10, 12
        case isaac_typescript_definitions_1.BackdropType.WOMB:
        case isaac_typescript_definitions_1.BackdropType.SCARRED_WOMB: {
            return "Props_07_The Womb.anm2";
        }
        // 11
        case isaac_typescript_definitions_1.BackdropType.UTERO: {
            return "Props_07_Utero.anm2";
        }
        // 13, 27
        case isaac_typescript_definitions_1.BackdropType.BLUE_WOMB:
        case isaac_typescript_definitions_1.BackdropType.BLUE_WOMB_PASS: {
            return "Props_07_The Womb_blue.anm2";
        }
        // 14, 47
        case isaac_typescript_definitions_1.BackdropType.SHEOL:
        case isaac_typescript_definitions_1.BackdropType.GEHENNA: {
            return "Props_09_Sheol.anm2";
        }
        // 15
        case isaac_typescript_definitions_1.BackdropType.CATHEDRAL: {
            return "Props_10_Cathedral.anm2";
        }
        // 17
        case isaac_typescript_definitions_1.BackdropType.CHEST: {
            return "Props_11_The Chest.anm2";
        }
        // 28
        case isaac_typescript_definitions_1.BackdropType.GREED_SHOP: {
            return "Props_12_Greed.anm2";
        }
        // 31
        case isaac_typescript_definitions_1.BackdropType.DOWNPOUR: {
            return "props_01x_downpour.anm2";
        }
        // 32, 46, 58, 59
        case isaac_typescript_definitions_1.BackdropType.MINES:
        case isaac_typescript_definitions_1.BackdropType.ASHPIT:
        case isaac_typescript_definitions_1.BackdropType.MINES_SHAFT:
        case isaac_typescript_definitions_1.BackdropType.ASHPIT_SHAFT: {
            return "props_03x_mines.anm2";
        }
        // 34, 43, 44, 48
        case isaac_typescript_definitions_1.BackdropType.CORPSE:
        case isaac_typescript_definitions_1.BackdropType.CORPSE_2:
        case isaac_typescript_definitions_1.BackdropType.CORPSE_3:
        case isaac_typescript_definitions_1.BackdropType.MORTIS: {
            return "props_07_the corpse.anm2";
        }
        // 45
        case isaac_typescript_definitions_1.BackdropType.DROSS: {
            return "props_02x_dross.anm2";
        }
        default: {
            return "Props_01_Basement.anm2";
        }
    }
}
/** Helper function to get the top left and bottom right corners of a given grid entity. */
function getGridEntityCollisionPoints(gridEntity) {
    const topLeft = Vector(gridEntity.Position.X - constants_1.DISTANCE_OF_GRID_TILE / 2, gridEntity.Position.Y - constants_1.DISTANCE_OF_GRID_TILE / 2);
    const bottomRight = Vector(gridEntity.Position.X + constants_1.DISTANCE_OF_GRID_TILE / 2, gridEntity.Position.Y + constants_1.DISTANCE_OF_GRID_TILE / 2);
    return { topLeft, bottomRight };
}
exports.getGridEntityCollisionPoints = getGridEntityCollisionPoints;
/** Helper function to get a string containing the grid entity's type and variant. */
function getGridEntityID(gridEntity) {
    const gridEntityType = gridEntity.GetType();
    const variant = gridEntity.GetVariant();
    return `${gridEntityType}.${variant}`;
}
exports.getGridEntityID = getGridEntityID;
/**
 * Helper function to get a formatted string in the format returned by the `getGridEntityID`
 * function.
 */
function getGridEntityIDFromConstituents(gridEntityType, variant) {
    return `${gridEntityType}.${variant}`;
}
exports.getGridEntityIDFromConstituents = getGridEntityIDFromConstituents;
/**
 * Helper function to get all of the grid entities in the room that specifically match the type and
 * variant provided.
 *
 * If you want to match every variant, use the `getGridEntities` function instead.
 */
function getMatchingGridEntities(gridEntityType, variant) {
    const gridEntities = getGridEntities(gridEntityType);
    return gridEntities.filter((gridEntity) => gridEntity.GetVariant() === variant);
}
exports.getMatchingGridEntities = getMatchingGridEntities;
/**
 * Helper function to get the PNG path for a rock. This depends on the current room's backdrop. The
 * values are taken from the "backdrops.xml" file.
 *
 * All of the rock PNGs are in the "gfx/grid" directory.
 */
function getRockPNGPath() {
    const rockPNGName = getRockPNGName();
    return `gfx/grid/${rockPNGName}`;
}
exports.getRockPNGPath = getRockPNGPath;
function getRockPNGName() {
    const room = cachedClasses_1.game.GetRoom();
    const backdropType = room.GetBackdropType();
    switch (backdropType) {
        // 1, 17
        case isaac_typescript_definitions_1.BackdropType.BASEMENT:
        case isaac_typescript_definitions_1.BackdropType.CHEST: {
            return "rocks_basement.png";
        }
        // 2
        case isaac_typescript_definitions_1.BackdropType.CELLAR: {
            return "rocks_cellar.png";
        }
        // 3
        case isaac_typescript_definitions_1.BackdropType.BURNING_BASEMENT: {
            return "rocks_burningbasement.png"; // cspell:ignore burningbasement
        }
        // 4
        case isaac_typescript_definitions_1.BackdropType.CAVES: {
            return "rocks_caves.png";
        }
        // 5
        case isaac_typescript_definitions_1.BackdropType.CATACOMBS: {
            return "rocks_catacombs.png";
        }
        // 6
        case isaac_typescript_definitions_1.BackdropType.FLOODED_CAVES: {
            return "rocks_drownedcaves.png"; // cspell:ignore drownedcaves
        }
        // 7, 8, 9, 30, 60
        case isaac_typescript_definitions_1.BackdropType.DEPTHS:
        case isaac_typescript_definitions_1.BackdropType.NECROPOLIS:
        case isaac_typescript_definitions_1.BackdropType.DANK_DEPTHS:
        case isaac_typescript_definitions_1.BackdropType.SACRIFICE:
        case isaac_typescript_definitions_1.BackdropType.DARK_CLOSET: {
            return "rocks_depths.png";
        }
        // 10
        case isaac_typescript_definitions_1.BackdropType.WOMB: {
            return "rocks_womb.png";
        }
        // 11
        case isaac_typescript_definitions_1.BackdropType.UTERO: {
            return "rocks_utero.png";
        }
        // 12
        case isaac_typescript_definitions_1.BackdropType.SCARRED_WOMB: {
            return "rocks_scarredwomb.png"; // cspell:ignore scarredwomb
        }
        // 13, 27
        case isaac_typescript_definitions_1.BackdropType.BLUE_WOMB:
        case isaac_typescript_definitions_1.BackdropType.BLUE_WOMB_PASS: {
            return "rocks_bluewomb.png"; // cspell:ignore bluewomb
        }
        // 14, 16
        case isaac_typescript_definitions_1.BackdropType.SHEOL:
        case isaac_typescript_definitions_1.BackdropType.DARK_ROOM: {
            return "rocks_sheol.png";
        }
        // 15, 35
        case isaac_typescript_definitions_1.BackdropType.CATHEDRAL:
        case isaac_typescript_definitions_1.BackdropType.PLANETARIUM: {
            return "rocks_cathedral.png";
        }
        // 23, 32, 37, 58
        case isaac_typescript_definitions_1.BackdropType.SECRET:
        case isaac_typescript_definitions_1.BackdropType.MINES:
        case isaac_typescript_definitions_1.BackdropType.MINES_ENTRANCE:
        case isaac_typescript_definitions_1.BackdropType.MINES_SHAFT: {
            return "rocks_secretroom.png"; // cspell:ignore secretroom
        }
        // 31, 36
        case isaac_typescript_definitions_1.BackdropType.DOWNPOUR:
        case isaac_typescript_definitions_1.BackdropType.DOWNPOUR_ENTRANCE: {
            return "rocks_downpour.png";
        }
        // 33, 38, 40, 41, 42
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_ENTRANCE:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_2:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_3:
        case isaac_typescript_definitions_1.BackdropType.MAUSOLEUM_4: {
            return "rocks_mausoleum.png";
        }
        // 34, 48
        case isaac_typescript_definitions_1.BackdropType.CORPSE:
        case isaac_typescript_definitions_1.BackdropType.MORTIS: {
            return "rocks_corpse.png";
        }
        // 39
        case isaac_typescript_definitions_1.BackdropType.CORPSE_ENTRANCE: {
            return "rocks_corpseentrance.png"; // cspell:ignore corpseentrance
        }
        // 43
        case isaac_typescript_definitions_1.BackdropType.CORPSE_2: {
            return "rocks_corpse2.png";
        }
        // 44
        case isaac_typescript_definitions_1.BackdropType.CORPSE_3: {
            return "rocks_corpse3.png";
        }
        // 45
        case isaac_typescript_definitions_1.BackdropType.DROSS: {
            return "rocks_dross.png";
        }
        // 46, 59
        case isaac_typescript_definitions_1.BackdropType.ASHPIT:
        case isaac_typescript_definitions_1.BackdropType.ASHPIT_SHAFT: {
            return "rocks_ashpit.png";
        }
        // 47
        case isaac_typescript_definitions_1.BackdropType.GEHENNA: {
            return "rocks_gehenna.png";
        }
        default: {
            return "rocks_basement.png";
        }
    }
}
/**
 * Helper function to get the grid entities on the surrounding tiles from the provided grid entity.
 *
 * For example, if a rock was surrounded by rocks on all sides, this would return an array of 8
 * rocks (e.g. top-left + top + top-right + left + right + bottom-left + bottom + right).
 */
function getSurroundingGridEntities(gridEntity) {
    const room = cachedClasses_1.game.GetRoom();
    const gridIndex = gridEntity.GetGridIndex();
    const surroundingGridIndexes = getSurroundingGridIndexes(gridIndex);
    const surroundingGridEntities = [];
    for (const surroundingGridIndex of surroundingGridIndexes) {
        const surroundingGridEntity = room.GetGridEntity(surroundingGridIndex);
        if (surroundingGridEntity !== undefined) {
            surroundingGridEntities.push(surroundingGridEntity);
        }
    }
    return surroundingGridEntities;
}
exports.getSurroundingGridEntities = getSurroundingGridEntities;
/**
 * Helper function to get the grid indexes on the surrounding tiles from the provided grid index.
 *
 * There are always 8 grid indexes returned (e.g. top-left + top + top-right + left + right +
 * bottom-left + bottom + right), even if the computed values would be negative or otherwise
 * invalid.
 */
function getSurroundingGridIndexes(gridIndex) {
    const room = cachedClasses_1.game.GetRoom();
    const gridWidth = room.GetGridWidth();
    return [
        gridIndex - gridWidth - 1,
        gridIndex - gridWidth,
        gridIndex - gridWidth + 1,
        gridIndex - 1,
        gridIndex + 1,
        gridIndex + gridWidth - 1,
        gridIndex + gridWidth,
        gridIndex + gridWidth + 1, // Bottom-right
    ];
}
exports.getSurroundingGridIndexes = getSurroundingGridIndexes;
/**
 * Helper function to get the top left wall in the current room.
 *
 * This function can be useful in certain situations to determine if the room is currently loaded.
 */
function getTopLeftWall() {
    const room = cachedClasses_1.game.GetRoom();
    const topLeftWallGridIndex = getTopLeftWallGridIndex();
    return room.GetGridEntity(topLeftWallGridIndex);
}
exports.getTopLeftWall = getTopLeftWall;
/**
 * Helper function to get the grid index of the top left wall. (This will depend on what the current
 * room shape is.)
 *
 * This function can be useful in certain situations to determine if the room is currently loaded.
 */
function getTopLeftWallGridIndex() {
    const room = cachedClasses_1.game.GetRoom();
    const roomShape = room.GetRoomShape();
    const topLeftWallGridIndex = roomShapeToTopLeftWallGridIndexMap_1.ROOM_SHAPE_TO_TOP_LEFT_WALL_GRID_INDEX_MAP.get(roomShape);
    return topLeftWallGridIndex ?? roomShapeToTopLeftWallGridIndexMap_1.DEFAULT_TOP_LEFT_WALL_GRID_INDEX;
}
exports.getTopLeftWallGridIndex = getTopLeftWallGridIndex;
/**
 * Helper function to detect if a particular grid entity would "break" if it was touched by an
 * explosion.
 *
 * For example, rocks and pots are breakable by explosions, but blocks are not.
 */
function isGridEntityBreakableByExplosion(gridEntity) {
    const gridEntityType = gridEntity.GetType();
    const variant = gridEntity.GetVariant();
    const gridEntityTypeVariant = `${gridEntityType}.${variant}`;
    return (BREAKABLE_GRID_ENTITY_TYPES_BY_EXPLOSIONS.has(gridEntityType) ||
        BREAKABLE_GRID_ENTITY_TYPES_VARIANTS_BY_EXPLOSIONS.has(gridEntityTypeVariant));
}
exports.isGridEntityBreakableByExplosion = isGridEntityBreakableByExplosion;
/**
 * Helper function to see if the provided grid entity is in its respective broken state. See the
 * `GRID_ENTITY_TYPE_TO_BROKEN_STATE_MAP` constant for more details.
 *
 * Note that in the case of `GridEntityType.LOCK` (11), the state will turn to being broken before
 * the actual collision for the entity is removed.
 */
function isGridEntityBroken(gridEntity) {
    const gridEntityType = gridEntity.GetType();
    const brokenState = gridEntityTypeToBrokenStateMap_1.GRID_ENTITY_TYPE_TO_BROKEN_STATE_MAP.get(gridEntityType);
    return gridEntity.State === brokenState;
}
exports.isGridEntityBroken = isGridEntityBroken;
/**
 * Helper function to see if an arbitrary number is a valid `GridEntityXMLType`. This is useful in
 * the `PRE_ROOM_ENTITY_SPAWN` callback for narrowing the type of the first argument.
 */
function isGridEntityXMLType(num) {
    return GRID_ENTITY_XML_TYPES_SET.has(num); // eslint-disable-line isaacscript/strict-enums
}
exports.isGridEntityXMLType = isGridEntityXMLType;
/**
 * Helper function to check if the provided grid index has a door on it or if the surrounding 8 grid
 * indexes have a door on it.
 */
function isGridIndexAdjacentToDoor(gridIndex) {
    const room = cachedClasses_1.game.GetRoom();
    const surroundingGridIndexes = getSurroundingGridIndexes(gridIndex);
    const gridIndexes = [gridIndex, ...surroundingGridIndexes];
    for (const gridIndexToInspect of gridIndexes) {
        const gridEntity = room.GetGridEntity(gridIndexToInspect);
        if (gridEntity !== undefined) {
            const door = gridEntity.ToDoor();
            if (door !== undefined) {
                return true;
            }
        }
    }
    return false;
}
exports.isGridIndexAdjacentToDoor = isGridIndexAdjacentToDoor;
/** Helper function to see if a `GridEntityXMLType` is some kind of poop. */
function isPoopGridEntityXMLType(gridEntityXMLType) {
    return poopGridEntityXMLTypesSet_1.POOP_GRID_ENTITY_XML_TYPES_SET.has(gridEntityXMLType);
}
exports.isPoopGridEntityXMLType = isPoopGridEntityXMLType;
/**
 * Helper function to detect whether a given Void Portal is one that randomly spawns after a boss is
 * defeated or is one that naturally spawns in the room after Hush.
 *
 * Under the hood, this is determined by looking at the `VarData` of the entity:
 * - The `VarData` of Void Portals that are spawned after bosses will be equal to 1.
 * - The `VarData` of the Void Portal in the room after Hush is equal to 0.
 */
function isPostBossVoidPortal(gridEntity) {
    const saveState = gridEntity.GetSaveState();
    return (saveState.Type === isaac_typescript_definitions_1.GridEntityType.TRAPDOOR &&
        saveState.Variant === isaac_typescript_definitions_1.TrapdoorVariant.VOID_PORTAL &&
        saveState.VarData === 1);
}
exports.isPostBossVoidPortal = isPostBossVoidPortal;
/**
 * Helper function to all grid entities in the room except for ones matching the grid entity types
 * provided.
 *
 * Note that this function will automatically update the room. (This means that you can spawn new
 * grid entities on the same tile on the same frame, if needed.)
 *
 * For example:
 *
 * ```ts
 * removeAllGridEntitiesExcept(
 *   GridEntityType.WALL,
 *   GridEntityType.DOOR,
 * );
 * ```
 *
 * @returns The grid entities that were removed.
 */
function removeAllGridEntitiesExcept(...gridEntityTypes) {
    const gridEntityTypeExceptions = new ReadonlySet_1.ReadonlySet(gridEntityTypes);
    const gridEntities = getGridEntities();
    const removedGridEntities = [];
    for (const gridEntity of gridEntities) {
        const gridEntityType = gridEntity.GetType();
        if (!gridEntityTypeExceptions.has(gridEntityType)) {
            removeGridEntity(gridEntity, false);
            removedGridEntities.push(gridEntity);
        }
    }
    if (removedGridEntities.length > 0) {
        (0, rooms_1.roomUpdateSafe)();
    }
    return removedGridEntities;
}
exports.removeAllGridEntitiesExcept = removeAllGridEntitiesExcept;
/**
 * Helper function to remove all of the grid entities in the room that match the grid entity types
 * provided.
 *
 * Note that this function will automatically update the room. (This means that you can spawn new
 * grid entities on the same tile on the same frame, if needed.)
 *
 * For example:
 *
 * ```ts
 * removeAllMatchingGridEntities(
 *   GridEntityType.ROCK,
 *   GridEntityType.BLOCK,
 *   GridEntityType.ROCK_TINTED,
 * );
 * ```
 *
 * @returns An array of the grid entities removed.
 */
function removeAllMatchingGridEntities(...gridEntityType) {
    const gridEntities = getGridEntities(...gridEntityType);
    if (gridEntities.length === 0) {
        return [];
    }
    for (const gridEntity of gridEntities) {
        removeGridEntity(gridEntity, false);
    }
    (0, rooms_1.roomUpdateSafe)();
    return gridEntities;
}
exports.removeAllMatchingGridEntities = removeAllMatchingGridEntities;
/**
 * Helper function to remove all entities that just spawned from a grid entity breaking.
 * Specifically, this is any entities that overlap with the position of a grid entity and are on
 * frame 0.
 *
 * You must specify an array of entities to look through.
 */
function removeEntitiesSpawnedFromGridEntity(entities, gridEntity) {
    const entitiesFromGridEntity = entities.filter((entity) => entity.FrameCount === 0 &&
        (0, vector_1.vectorEquals)(entity.Position, gridEntity.Position));
    (0, entities_1.removeEntities)(entitiesFromGridEntity);
}
exports.removeEntitiesSpawnedFromGridEntity = removeEntitiesSpawnedFromGridEntity;
/**
 * Helper function to remove all of the grid entities in the supplied array.
 *
 * @param gridEntities The array of grid entities to remove.
 * @param updateRoom Whether to update the room after the grid entities are removed. This is
 *                   generally a good idea because if the room is not updated, you will be unable to
 *                   spawn another grid entity on the same tile until a frame has passed. However,
 *                   doing this is expensive, since it involves a call to `Isaac.GetRoomEntities`,
 *                   so set this to false if you need to run this function multiple times.
 * @param cap Optional. If specified, will only remove the given amount of entities.
 * @returns An array of the entities that were removed.
 */
function removeGridEntities(gridEntities, updateRoom, cap) {
    if (gridEntities.length === 0) {
        return [];
    }
    const gridEntitiesRemoved = [];
    for (const gridEntity of gridEntities) {
        removeGridEntity(gridEntity, false);
        gridEntitiesRemoved.push(gridEntity);
        if (cap !== undefined && gridEntitiesRemoved.length >= cap) {
            break;
        }
    }
    if (updateRoom) {
        (0, rooms_1.roomUpdateSafe)();
    }
    return gridEntitiesRemoved;
}
exports.removeGridEntities = removeGridEntities;
/**
 * Helper function to remove a grid entity by providing the grid entity object or the grid index
 * inside of the room.
 *
 * If removing a Devil Statue or an Angel Statue, this will also remove the associated effect
 * (`EffectVariant.DEVIL` (6) or `EffectVariant.ANGEL` (9), respectively.)
 *
 * @param gridEntityOrGridIndex The grid entity or grid index to remove.
 * @param updateRoom Whether to update the room after the grid entity is removed. This is generally
 *                   a good idea because if the room is not updated, you will be unable to spawn
 *                   another grid entity on the same tile until a frame has passed. However, doing
 *                   this is expensive, since it involves a call to `Isaac.GetRoomEntities`, so set
 *                   this to false if you need to run this function multiple times.
 */
function removeGridEntity(gridEntityOrGridIndex, updateRoom) {
    const room = cachedClasses_1.game.GetRoom();
    const gridEntity = (0, types_1.isInteger)(gridEntityOrGridIndex)
        ? room.GetGridEntity(gridEntityOrGridIndex)
        : gridEntityOrGridIndex;
    if (gridEntity === undefined) {
        // There is no grid entity to remove.
        return;
    }
    const gridEntityType = gridEntity.GetType();
    const variant = gridEntity.GetVariant();
    const position = gridEntity.Position;
    const gridIndex = (0, types_1.isInteger)(gridEntityOrGridIndex)
        ? gridEntityOrGridIndex
        : gridEntityOrGridIndex.GetGridIndex();
    room.RemoveGridEntity(gridIndex, 0, false);
    if (updateRoom) {
        (0, rooms_1.roomUpdateSafe)();
    }
    // In the special case of removing a Devil Statue or Angel Statue, we also need to delete the
    // corresponding effect.
    if (gridEntityType === isaac_typescript_definitions_1.GridEntityType.STATUE) {
        const effectVariant = variant === isaac_typescript_definitions_1.StatueVariant.DEVIL
            ? isaac_typescript_definitions_1.EffectVariant.DEVIL
            : isaac_typescript_definitions_1.EffectVariant.ANGEL;
        const effects = (0, entitiesSpecific_1.getEffects)(effectVariant);
        const effectsOnTile = effects.filter((effect) => (0, vector_1.vectorEquals)(effect.Position, position));
        (0, entities_1.removeEntities)(effectsOnTile);
    }
}
exports.removeGridEntity = removeGridEntity;
/**
 * Helper function to make a grid entity invisible. This is accomplished by resetting the sprite.
 *
 * Note that this function is destructive such that once you make a grid entity invisible, it can no
 * longer become visible. (This is because the information about the sprite is lost when it is
 * reset.)
 */
function setGridEntityInvisible(gridEntity) {
    const sprite = gridEntity.GetSprite();
    sprite.Reset();
}
exports.setGridEntityInvisible = setGridEntityInvisible;
/**
 * Helper function to change the type of a grid entity to another type. Use this instead of the
 * `GridEntity.SetType` method since that does not properly handle updating the sprite of the grid
 * entity after the type is changed.
 *
 * Setting the new type to `GridEntityType.NULL` (0) will have no effect.
 */
function setGridEntityType(gridEntity, gridEntityType) {
    gridEntity.SetType(gridEntityType);
    const sprite = gridEntity.GetSprite();
    const anm2Path = getGridEntityANM2Path(gridEntityType);
    if (anm2Path === undefined) {
        return;
    }
    sprite.Load(anm2Path, false);
    if (gridEntityType === isaac_typescript_definitions_1.GridEntityType.ROCK) {
        const pngPath = getRockPNGPath();
        sprite.ReplaceSpritesheet(0, pngPath);
    }
    sprite.LoadGraphics();
    const defaultAnimation = sprite.GetDefaultAnimation();
    sprite.Play(defaultAnimation, true);
}
exports.setGridEntityType = setGridEntityType;
/**
 * Helper function to spawn a giant poop. This is performed by spawning each of the four quadrant
 * grid entities in the appropriate positions.
 *
 * @returns Whether spawning the four quadrants was successful.
 */
function spawnGiantPoop(topLeftGridIndex) {
    const room = cachedClasses_1.game.GetRoom();
    const gridWidth = room.GetGridWidth();
    const topRightGridIndex = topLeftGridIndex + 1;
    const bottomLeftGridIndex = topLeftGridIndex + gridWidth;
    const bottomRightGridIndex = bottomLeftGridIndex + 1;
    // First, check to see if all of the tiles are open.
    for (const gridIndex of [
        topLeftGridIndex,
        topRightGridIndex,
        bottomLeftGridIndex,
        bottomRightGridIndex,
    ]) {
        const gridEntity = room.GetGridEntity(gridIndex);
        if (gridEntity !== undefined) {
            return false;
        }
    }
    const topLeft = spawnGridEntityWithVariant(isaac_typescript_definitions_1.GridEntityType.POOP, isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_TOP_LEFT, topLeftGridIndex);
    const topRight = spawnGridEntityWithVariant(isaac_typescript_definitions_1.GridEntityType.POOP, isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_TOP_RIGHT, topRightGridIndex);
    const bottomLeft = spawnGridEntityWithVariant(isaac_typescript_definitions_1.GridEntityType.POOP, isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_BOTTOM_LEFT, bottomLeftGridIndex);
    const bottomRight = spawnGridEntityWithVariant(isaac_typescript_definitions_1.GridEntityType.POOP, isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_BOTTOM_RIGHT, bottomRightGridIndex);
    return (topLeft !== undefined &&
        topLeft.GetType() === isaac_typescript_definitions_1.GridEntityType.POOP &&
        topLeft.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_TOP_LEFT &&
        topRight !== undefined &&
        topRight.GetType() === isaac_typescript_definitions_1.GridEntityType.POOP &&
        topRight.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_TOP_RIGHT &&
        bottomLeft !== undefined &&
        bottomLeft.GetType() === isaac_typescript_definitions_1.GridEntityType.POOP &&
        bottomLeft.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_BOTTOM_LEFT &&
        bottomRight !== undefined &&
        bottomRight.GetType() === isaac_typescript_definitions_1.GridEntityType.POOP &&
        bottomRight.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_BOTTOM_RIGHT);
}
exports.spawnGiantPoop = spawnGiantPoop;
/**
 * Helper function to spawn a grid entity with a specific type.
 *
 * This function assumes you want to give the grid entity a variant of 0. If you want to specify a
 * variant, use the `spawnGridEntityWithVariant` helper function instead.
 *
 * Use this instead of the `Isaac.GridSpawn` method since it:
 * - handles giving pits collision
 * - removes existing grid entities on the same tile, if any
 * - allows you to specify either the grid index or the position
 *
 * @param gridEntityType The `GridEntityType` to use.
 * @param gridIndexOrPosition The grid index or position in the room that you want to spawn the grid
 *                            entity at. If a position is specified, the closest grid index will be
 *                            used.
 * @param removeExistingGridEntity Optional. Whether to remove the existing grid entity on the same
 *                                 tile, if it exists. Defaults to true. If false, this function
 *                                 will do nothing, since spawning a grid entity on top of another
 *                                 grid entity will not replace it.
 */
function spawnGridEntity(gridEntityType, gridIndexOrPosition, removeExistingGridEntity = true) {
    return spawnGridEntityWithVariant(gridEntityType, 0, gridIndexOrPosition, removeExistingGridEntity);
}
exports.spawnGridEntity = spawnGridEntity;
/**
 * Helper function to spawn a grid entity with a specific variant.
 *
 * Use this instead of the `Isaac.GridSpawn` method since it:
 * - handles giving pits collision
 * - removes existing grid entities on the same tile, if any
 * - allows you to specify the grid index or the position
 *
 * @param gridEntityType The `GridEntityType` to use.
 * @param variant The variant to use.
 * @param gridIndexOrPosition The grid index or position in the room that you want to spawn the grid
 *                            entity at. If a position is specified, the closest grid index will be
 *                            used.
 * @param removeExistingGridEntity Optional. Whether to remove the existing grid entity on the same
 *                                 tile, if it exists. Defaults to true. If false, this function
 *                                 will do nothing, since spawning a grid entity on top of another
 *                                 grid entity will not replace it.
 */
function spawnGridEntityWithVariant(gridEntityType, variant, gridIndexOrPosition, removeExistingGridEntity = true) {
    const room = cachedClasses_1.game.GetRoom();
    const existingGridEntity = (0, vector_1.isVector)(gridIndexOrPosition)
        ? room.GetGridEntityFromPos(gridIndexOrPosition)
        : room.GetGridEntity(gridIndexOrPosition);
    if (existingGridEntity !== undefined) {
        if (removeExistingGridEntity) {
            removeGridEntity(existingGridEntity, true);
        }
        else {
            return undefined;
        }
    }
    const position = (0, vector_1.isVector)(gridIndexOrPosition)
        ? gridIndexOrPosition
        : room.GetGridPosition(gridIndexOrPosition);
    const gridEntity = Isaac.GridSpawn(gridEntityType, variant, position);
    if (gridEntity === undefined) {
        return gridEntity;
    }
    if (gridEntityType === isaac_typescript_definitions_1.GridEntityType.PIT) {
        // For some reason, spawned pits start with a collision class of `NONE`, so we have to manually
        // set it.
        const pit = gridEntity.ToPit();
        if (pit !== undefined) {
            pit.UpdateCollision();
        }
    }
    else if (gridEntityType === isaac_typescript_definitions_1.GridEntityType.WALL) {
        // For some reason, spawned walls start with a collision class of `NONE`, so we have to manually
        // set it.
        gridEntity.CollisionClass = isaac_typescript_definitions_1.GridCollisionClass.WALL;
    }
    return gridEntity;
}
exports.spawnGridEntityWithVariant = spawnGridEntityWithVariant;
/**
 * Helper function to spawn a Void Portal. This is more complicated than simply spawning a trapdoor
 * with the appropriate variant, as the game does not give it the correct sprite automatically.
 */
function spawnVoidPortal(gridIndex) {
    const voidPortal = spawnGridEntityWithVariant(isaac_typescript_definitions_1.GridEntityType.TRAPDOOR, isaac_typescript_definitions_1.TrapdoorVariant.VOID_PORTAL, gridIndex);
    if (voidPortal === undefined) {
        return voidPortal;
    }
    // If Void Portals are not given a VarData of 1, they will send the player to the next floor
    // instead of The Void.
    voidPortal.VarData = 1;
    const sprite = voidPortal.GetSprite();
    sprite.Load("gfx/grid/voidtrapdoor.anm2", true);
    return voidPortal;
}
exports.spawnVoidPortal = spawnVoidPortal;
