"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeDoors = exports.removeDoor = exports.removeAllDoors = exports.openDoorFast = exports.openAllDoors = exports.lockDoor = exports.isVoidDoor = exports.isSecretRoomDoor = exports.isRepentanceDoor = exports.isMegaSatanDoor = exports.isHiddenSecretRoomDoor = exports.isDoorToMomsHeart = exports.isDoorToMines = exports.isDoorToMausoleumAscent = exports.isDoorToMausoleum = exports.isDoorToDownpour = exports.isDoorSlotInRoomShape = exports.isDevilRoomDoor = exports.isBossRushDoor = exports.isBlueWombDoor = exports.isAngelRoomDoor = exports.hasUnusedDoorSlot = exports.hasDoorType = exports.getVoidDoor = exports.getUnusedDoorSlots = exports.getRoomShapeDoorSlotCoordinates = exports.getRoomShapeDoorSlot = exports.getRepentanceDoor = exports.getOppositeDoorSlot = exports.getMegaSatanDoor = exports.getDoorsToRoomIndex = exports.getDoors = exports.getDoorSlotsForRoomShape = exports.getDoorSlotEnterPositionOffset = exports.getDoorSlotEnterPosition = exports.getDoorEnterPosition = exports.getDevilRoomOrAngelRoomDoor = exports.getDevilRoomDoor = exports.getBossRushDoor = exports.getBlueWombDoor = exports.getAngelRoomDoor = exports.doorSlotsToDoorSlotFlags = exports.doorSlotToDoorSlotFlag = exports.doorSlotToDirection = exports.doorSlotFlagsToDoorSlots = exports.doorSlotFlagToDoorSlot = exports.closeDoorFast = exports.closeAllDoors = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedEnumValues_1 = require("../arrays/cachedEnumValues");
const cachedClasses_1 = require("../core/cachedClasses");
const constants_1 = require("../core/constants");
const doorSlotFlagToDoorSlot_1 = require("../objects/doorSlotFlagToDoorSlot");
const doorSlotToDirection_1 = require("../objects/doorSlotToDirection");
const doorSlotToDoorSlotFlag_1 = require("../objects/doorSlotToDoorSlotFlag");
const oppositeDoorSlots_1 = require("../objects/oppositeDoorSlots");
const roomShapeToDoorSlotCoordinates_1 = require("../objects/roomShapeToDoorSlotCoordinates");
const roomShapeToDoorSlots_1 = require("../objects/roomShapeToDoorSlots");
const ReadonlySet_1 = require("../types/ReadonlySet");
const bitwise_1 = require("./bitwise");
const direction_1 = require("./direction");
const enums_1 = require("./enums");
const flag_1 = require("./flag");
const tstlClass_1 = require("./tstlClass");
const types_1 = require("./types");
function closeAllDoors() {
    for (const door of getDoors()) {
        door.Close(true);
    }
}
exports.closeAllDoors = closeAllDoors;
/**
 * Use this instead of the `GridEntityDoor.Close` method if you want the door to immediately close
 * without an animation.
 */
function closeDoorFast(door) {
    door.State = isaac_typescript_definitions_1.DoorState.CLOSED;
    const sprite = door.GetSprite();
    sprite.Play("Closed", true);
}
exports.closeDoorFast = closeDoorFast;
function doorSlotFlagToDoorSlot(doorSlotFlag) {
    const doorSlot = doorSlotFlagToDoorSlot_1.DOOR_SLOT_FLAG_TO_DOOR_SLOT[doorSlotFlag];
    return doorSlot ?? doorSlotFlagToDoorSlot_1.DEFAULT_DOOR_SLOT;
}
exports.doorSlotFlagToDoorSlot = doorSlotFlagToDoorSlot;
function doorSlotFlagsToDoorSlots(doorSlotFlags) {
    const doorSlots = [];
    for (const doorSlotFlag of cachedEnumValues_1.DOOR_SLOT_FLAG_VALUES) {
        if ((0, flag_1.hasFlag)(doorSlotFlags, doorSlotFlag)) {
            const doorSlot = doorSlotFlagToDoorSlot(doorSlotFlag);
            doorSlots.push(doorSlot);
        }
    }
    return doorSlots;
}
exports.doorSlotFlagsToDoorSlots = doorSlotFlagsToDoorSlots;
function doorSlotToDirection(doorSlot) {
    return doorSlotToDirection_1.DOOR_SLOT_TO_DIRECTION[doorSlot];
}
exports.doorSlotToDirection = doorSlotToDirection;
function doorSlotToDoorSlotFlag(doorSlot) {
    return doorSlotToDoorSlotFlag_1.DOOR_SLOT_TO_DOOR_SLOT_FLAG[doorSlot];
}
exports.doorSlotToDoorSlotFlag = doorSlotToDoorSlotFlag;
/**
 * Helper function to convert an array of door slots or a set of door slots to the resulting bit
 * flag number.
 */
function doorSlotsToDoorSlotFlags(doorSlots) {
    const doorSlotsMutable = doorSlots;
    const doorSlotArray = (0, tstlClass_1.isTSTLSet)(doorSlotsMutable)
        ? [...doorSlotsMutable.values()]
        : doorSlotsMutable;
    const doorSlotFlagArray = doorSlotArray.map((doorSlot) => doorSlotToDoorSlotFlag(doorSlot));
    return (0, bitwise_1.arrayToBitFlags)(doorSlotFlagArray);
}
exports.doorSlotsToDoorSlotFlags = doorSlotsToDoorSlotFlags;
function getAngelRoomDoor() {
    const angelRoomDoors = getDoors(isaac_typescript_definitions_1.RoomType.ANGEL);
    return angelRoomDoors.length === 0 ? undefined : angelRoomDoors[0];
}
exports.getAngelRoomDoor = getAngelRoomDoor;
/**
 * Helper function to get the door that leads to the off-grid room that contains the hole to the
 * Blue Womb. (In vanilla, the door will only appear in the It Lives Boss Room.)
 *
 * Returns undefined if the room has no Blue Womb doors.
 */
function getBlueWombDoor() {
    const doors = getDoors();
    return doors.find((door) => isBlueWombDoor(door));
}
exports.getBlueWombDoor = getBlueWombDoor;
/**
 * Helper function to get the door that leads to the Boss Rush. (In vanilla, the door will only
 * appear in the Boss Room of the sixth floor.)
 *
 * Returns undefined if the room has no Boss Rush doors.
 */
function getBossRushDoor() {
    const doors = getDoors();
    return doors.find((door) => isBossRushDoor(door));
}
exports.getBossRushDoor = getBossRushDoor;
function getDevilRoomDoor() {
    const devilRoomDoors = getDoors(isaac_typescript_definitions_1.RoomType.DEVIL);
    return devilRoomDoors.length === 0 ? undefined : devilRoomDoors[0];
}
exports.getDevilRoomDoor = getDevilRoomDoor;
/**
 * If there is both a Devil Room and an Angel Room door, this function will return door with the
 * lowest slot number.
 */
function getDevilRoomOrAngelRoomDoor() {
    const devilRoomOrAngelRoomDoors = getDoors(isaac_typescript_definitions_1.RoomType.DEVIL, isaac_typescript_definitions_1.RoomType.ANGEL);
    return devilRoomOrAngelRoomDoors.length === 0
        ? undefined
        : devilRoomOrAngelRoomDoors[0];
}
exports.getDevilRoomOrAngelRoomDoor = getDevilRoomOrAngelRoomDoor;
/**
 * Helper function to get the position that a player will enter a room at corresponding to a door.
 *
 * When players enter a room, they do not appear exactly on the location of the door, because then
 * they would immediately collide with the loading zone. Instead, they appear on the grid tile next
 * to the door.
 */
function getDoorEnterPosition(door) {
    const offset = getDoorSlotEnterPositionOffset(door.Slot);
    return door.Position.add(offset);
}
exports.getDoorEnterPosition = getDoorEnterPosition;
/**
 * Helper function to get the position that a player will enter a room at corresponding to a door
 * slot.
 *
 * When players enter a room, they do not appear exactly on the location of the door, because then
 * they would immediately collide with the loading zone. Instead, they appear on the grid tile next
 * to the door.
 */
function getDoorSlotEnterPosition(doorSlot) {
    const room = cachedClasses_1.game.GetRoom();
    const position = room.GetDoorSlotPosition(doorSlot);
    const offset = getDoorSlotEnterPositionOffset(doorSlot);
    return position.add(offset);
}
exports.getDoorSlotEnterPosition = getDoorSlotEnterPosition;
/**
 * Helper function to get the offset from a door position that a player will enter a room at.
 *
 * When players enter a room, they do not appear exactly on the location of the door, because then
 * they would immediately collide with the loading zone. Instead, they appear on the grid tile next
 * to the door.
 */
function getDoorSlotEnterPositionOffset(doorSlot) {
    const direction = doorSlotToDirection(doorSlot);
    const vector = (0, direction_1.directionToVector)(direction);
    // The player appears in the opposite direction of the way that the door is oriented in the room.
    const oppositeVector = vector.mul(-1);
    return oppositeVector.mul(constants_1.DISTANCE_OF_GRID_TILE);
}
exports.getDoorSlotEnterPositionOffset = getDoorSlotEnterPositionOffset;
/** Helper function to get the possible door slots that can exist for a given room shape. */
function getDoorSlotsForRoomShape(roomShape) {
    return roomShapeToDoorSlots_1.ROOM_SHAPE_TO_DOOR_SLOTS[roomShape];
}
exports.getDoorSlotsForRoomShape = getDoorSlotsForRoomShape;
/**
 * Helper function to get all of the doors in the room. By default, it will return every door.
 *
 * You can optionally specify one or more room types to return only the doors that match the
 * specified room types.
 *
 * @allowEmptyVariadic
 */
function getDoors(...roomTypes) {
    const room = cachedClasses_1.game.GetRoom();
    const roomShape = room.GetRoomShape();
    const roomTypesSet = new ReadonlySet_1.ReadonlySet(roomTypes);
    // We iterate over the possible door slots for this room shape instead of all door slots in order
    // to prevent crashes from accessing invalid memory.
    const possibleDoorSlots = getDoorSlotsForRoomShape(roomShape);
    const doors = [];
    for (const doorSlot of possibleDoorSlots) {
        const door = room.GetDoor(doorSlot);
        if (door === undefined) {
            continue;
        }
        // In Repentance, sometimes doors won't be doors for some reason.
        const gridEntityType = door.GetType();
        if (gridEntityType !== isaac_typescript_definitions_1.GridEntityType.DOOR) {
            continue;
        }
        if (roomTypesSet.size === 0 || roomTypesSet.has(door.TargetRoomType)) {
            doors.push(door);
        }
    }
    return doors;
}
exports.getDoors = getDoors;
/**
 * Helper function to get all of the doors in the room that lead to the provided room index.
 *
 * This function is variadic, meaning that you can specify N arguments to return all of the doors
 * that match any of the N room grid indexes.
 */
function getDoorsToRoomIndex(...roomGridIndex) {
    const roomGridIndexesSet = new ReadonlySet_1.ReadonlySet(roomGridIndex);
    const doors = getDoors();
    return doors.filter((door) => roomGridIndexesSet.has(door.TargetRoomIndex));
}
exports.getDoorsToRoomIndex = getDoorsToRoomIndex;
/**
 * Helper function to get the door that leads to the Mega Satan Boss Room. (In vanilla, the door
 * will only appear in the starting room of The Chest / Dark Room.)
 *
 * Returns undefined if the room has no Mega Satan doors.
 */
function getMegaSatanDoor() {
    const doors = getDoors();
    return doors.find((door) => isMegaSatanDoor(door));
}
exports.getMegaSatanDoor = getMegaSatanDoor;
function getOppositeDoorSlot(doorSlot) {
    return oppositeDoorSlots_1.OPPOSITE_DOOR_SLOTS[doorSlot];
}
exports.getOppositeDoorSlot = getOppositeDoorSlot;
/**
 * Helper function to get the door that leads to the "secret exit" off-grid room that takes you to
 * the Repentance floor or to the version of Depths 2 that has Dad's Key.
 *
 * Returns undefined if the room has no Repentance doors.
 */
function getRepentanceDoor() {
    const doors = getDoors();
    return doors.find((door) => isRepentanceDoor(door));
}
exports.getRepentanceDoor = getRepentanceDoor;
/**
 * Helper function to get the corresponding door slot for a given room shape and grid coordinates.
 */
function getRoomShapeDoorSlot(roomShape, x, y) {
    const doorSlotCoordinates = roomShapeToDoorSlotCoordinates_1.ROOM_SHAPE_TO_DOOR_SLOT_COORDINATES[roomShape];
    for (const [doorSlotString, coordinates] of Object.entries(doorSlotCoordinates)) {
        const doorSlot = (0, types_1.parseIntSafe)(doorSlotString);
        if (doorSlot === undefined || !(0, enums_1.isEnumValue)(doorSlot, isaac_typescript_definitions_1.DoorSlot)) {
            continue;
        }
        const [doorX, doorY] = coordinates;
        if (x === doorX && y === doorY) {
            return doorSlot;
        }
    }
    return undefined;
}
exports.getRoomShapeDoorSlot = getRoomShapeDoorSlot;
/**
 * Helper function to get the room grid coordinates for a specific room shape and door slot
 * combination.
 */
function getRoomShapeDoorSlotCoordinates(roomShape, doorSlot) {
    const doorSlotCoordinates = roomShapeToDoorSlotCoordinates_1.ROOM_SHAPE_TO_DOOR_SLOT_COORDINATES[roomShape];
    return doorSlotCoordinates[doorSlot];
}
exports.getRoomShapeDoorSlotCoordinates = getRoomShapeDoorSlotCoordinates;
/**
 * Helper function to find unused door slots in the current room that can be used to make custom
 * doors.
 */
function getUnusedDoorSlots() {
    const room = cachedClasses_1.game.GetRoom();
    return cachedEnumValues_1.DOOR_SLOT_VALUES.filter((doorSlot) => 
    // We need to filter out the -1 value to prevent crashes.
    doorSlot !== isaac_typescript_definitions_1.DoorSlot.NO_DOOR_SLOT &&
        room.IsDoorSlotAllowed(doorSlot) &&
        room.GetDoor(doorSlot) === undefined);
}
exports.getUnusedDoorSlots = getUnusedDoorSlots;
/**
 * Helper function to get the door that leads to the off-grid room that contains the portal to The
 * Void. (In vanilla, the door will only appear in the Hush Boss Room.)
 *
 * Returns undefined if the room has no Void doors.
 */
function getVoidDoor() {
    const doors = getDoors();
    return doors.find((door) => isVoidDoor(door));
}
exports.getVoidDoor = getVoidDoor;
/**
 * Helper function to check if the current room has one or more doors that lead to the given room
 * type.
 *
 * This function is variadic, meaning that you can supply as many door types as you want to check
 * for. This function will return true if one or more room types match.
 */
function hasDoorType(...roomTypes) {
    const doors = getDoors();
    const doorsOfThisRoomType = doors.filter((door) => roomTypes.some((roomType) => door.IsRoomType(roomType)));
    return doorsOfThisRoomType.length > 0;
}
exports.hasDoorType = hasDoorType;
/**
 * Helper function to check if the current room has one or more open door slots that can be used to
 * make custom doors.
 */
function hasUnusedDoorSlot() {
    const unusedDoorSlots = getUnusedDoorSlots();
    return unusedDoorSlots.length > 0;
}
exports.hasUnusedDoorSlot = hasUnusedDoorSlot;
function isAngelRoomDoor(door) {
    return door.TargetRoomType === isaac_typescript_definitions_1.RoomType.ANGEL;
}
exports.isAngelRoomDoor = isAngelRoomDoor;
/**
 * Helper function to check if the provided door is the one that leads to the off-grid room that
 * contains the hole to the Blue Womb. (In vanilla, the door will only appear in the It Lives Boss
 * Room.)
 */
function isBlueWombDoor(door) {
    return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.BLUE_WOMB;
}
exports.isBlueWombDoor = isBlueWombDoor;
/**
 * Helper function to check if the provided door is the one that leads to the Boss Rush room. (In
 * vanilla, the door will only appear in the Boss Room of the sixth floor.)
 */
function isBossRushDoor(door) {
    return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.BOSS_RUSH;
}
exports.isBossRushDoor = isBossRushDoor;
function isDevilRoomDoor(door) {
    return door.TargetRoomType === isaac_typescript_definitions_1.RoomType.DEVIL;
}
exports.isDevilRoomDoor = isDevilRoomDoor;
/** Helper function to see if a door slot could exist for a given room shape. */
function isDoorSlotInRoomShape(doorSlot, roomShape) {
    const doorSlots = getDoorSlotsForRoomShape(roomShape);
    return doorSlots.has(doorSlot);
}
exports.isDoorSlotInRoomShape = isDoorSlotInRoomShape;
/**
 * This refers to the Repentance door that spawns in a boss room after defeating the boss. You have
 * to spend one key to open it. It has a sprite filename of "gfx/grid/door_downpour.anm2".
 */
function isDoorToDownpour(door) {
    if (!isRepentanceDoor(door)) {
        return false;
    }
    const sprite = door.GetSprite();
    const fileName = sprite.GetFilename();
    // On Windows, this is: "gfx/grid/Door_Downpour.anm2"
    return fileName.toLowerCase() === "gfx/grid/door_downpour.anm2";
}
exports.isDoorToDownpour = isDoorToDownpour;
/**
 * This refers to the Repentance door that spawns in a boss room after defeating the boss. You have
 * to spend two hearts to open it. It has a sprite filename of "gfx/grid/door_mausoleum.anm2".
 */
function isDoorToMausoleum(door) {
    if (!isRepentanceDoor(door)) {
        return false;
    }
    const sprite = door.GetSprite();
    const fileName = sprite.GetFilename();
    // On Windows, this is: "gfx/grid/Door_Mausoleum.anm2"
    return fileName.toLowerCase() === "gfx/grid/door_mausoleum.anm2";
}
exports.isDoorToMausoleum = isDoorToMausoleum;
/**
 * This refers to the "strange door" located on the first room of Depths 2. You open it with either
 * a Polaroid or a Negative. It has a sprite filename of "gfx/grid/door_mausoleum_alt.anm2".
 */
function isDoorToMausoleumAscent(door) {
    if (!isRepentanceDoor(door)) {
        return false;
    }
    const sprite = door.GetSprite();
    const fileName = sprite.GetFilename();
    // On Windows, this is: "gfx/grid/Door_Mausoleum_Alt.anm2"
    return fileName.toLowerCase() === "gfx/grid/door_mausoleum_alt.anm2";
}
exports.isDoorToMausoleumAscent = isDoorToMausoleumAscent;
/**
 * This refers to the Repentance door that spawns in a boss room after defeating the boss. You have
 * to spend two bombs to open it. It has a sprite filename of "gfx/grid/door_mines.anm2".
 */
function isDoorToMines(door) {
    if (!isRepentanceDoor(door)) {
        return false;
    }
    const sprite = door.GetSprite();
    const fileName = sprite.GetFilename();
    // On Windows, this is: "gfx/grid/Door_Mines.anm2"
    return fileName.toLowerCase() === "gfx/grid/door_mines.anm2";
}
exports.isDoorToMines = isDoorToMines;
/**
 * This refers to the Repentance door that spawns after defeating Mom. You open it with the
 * completed knife. It has a sprite filename of "gfx/grid/door_momsheart.anm2".
 */
function isDoorToMomsHeart(door) {
    if (!isRepentanceDoor(door)) {
        return false;
    }
    const sprite = door.GetSprite();
    const fileName = sprite.GetFilename();
    // On Windows, this is: "gfx/grid/Door_MomsHeart.anm2"
    return fileName.toLowerCase() === "gfx/grid/door_momsheart.anm2"; // cspell:ignore momsheart
}
exports.isDoorToMomsHeart = isDoorToMomsHeart;
function isHiddenSecretRoomDoor(door) {
    const sprite = door.GetSprite();
    const animation = sprite.GetAnimation();
    return isSecretRoomDoor(door) && animation === "Hidden";
}
exports.isHiddenSecretRoomDoor = isHiddenSecretRoomDoor;
/**
 * Helper function to check if the provided door is the one that leads to the Mega Satan Boss Room.
 * (In vanilla, the door will only appear in the starting room of The Chest / Dark Room.)
 */
function isMegaSatanDoor(door) {
    return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.MEGA_SATAN;
}
exports.isMegaSatanDoor = isMegaSatanDoor;
/**
 * Helper function to check if the provided door leads to the "secret exit" off-grid room that takes
 * you to the Repentance floor.
 */
function isRepentanceDoor(door) {
    return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.SECRET_EXIT;
}
exports.isRepentanceDoor = isRepentanceDoor;
/**
 * This refers to the hole in the wall that appears after bombing the entrance to a secret room.
 * Note that the door still exists before it has been bombed open. It has a sprite filename of
 * "gfx/grid/door_08_holeinwall.anm2".
 *
 * Note that since Ultra Secret Rooms do not use holes, this function will not detect an Ultra
 * Secret Room door.
 */
function isSecretRoomDoor(door) {
    const sprite = door.GetSprite();
    const fileName = sprite.GetFilename();
    // On Windows, this is: "gfx/grid/Door_08_HoleInWall.anm2"
    return fileName.toLowerCase() === "gfx/grid/door_08_holeinwall.anm2"; // cspell:ignore holeinwall
}
exports.isSecretRoomDoor = isSecretRoomDoor;
/**
 * Helper function to check if the provided door is the one that leads to the off-grid room that
 * contains the portal to The Void. (In vanilla, the door will only appear in the Hush Boss Room.)
 */
function isVoidDoor(door) {
    return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.VOID;
}
exports.isVoidDoor = isVoidDoor;
/**
 * Helper function to reset an unlocked door back to a locked state. Doing this is non-trivial
 * because in addition to calling the `GridEntityDoor.SetLocked` method, you must also:
 *
 * - Set the `VisitedCount` of the room's `RoomDescription` to 0.
 * - Set the variant to `DoorVariant.DOOR_LOCKED`.
 * - Close the door.
 */
function lockDoor(door) {
    const level = cachedClasses_1.game.GetLevel();
    // We can't use the "getRoomDescriptor" function since it will cause a dependency cycle.
    const roomDescriptor = level.GetRoomByIdx(door.TargetRoomIndex);
    roomDescriptor.VisitedCount = 0;
    door.SetVariant(isaac_typescript_definitions_1.DoorVariant.LOCKED);
    door.SetLocked(true);
    door.Close(true);
}
exports.lockDoor = lockDoor;
/**
 * For the purposes of this function, doors to Secret Rooms or Super Secret Rooms that have not been
 * discovered yet will not be opened.
 */
function openAllDoors() {
    for (const door of getDoors()) {
        // If we try to open a hidden Secret Room door (or Super Secret Room door), then nothing will
        // happen.
        door.Open();
    }
}
exports.openAllDoors = openAllDoors;
/**
 * Use this instead of the `GridEntityDoor.Open` method if you want the door to immediately open
 * without an animation.
 */
function openDoorFast(door) {
    door.State = isaac_typescript_definitions_1.DoorState.OPEN;
    const sprite = door.GetSprite();
    sprite.Play("Opened", true);
}
exports.openDoorFast = openDoorFast;
/**
 * Helper function to remove all of the doors in the room. By default, it will remove every door.
 * You can optionally specify one or more room types to remove only the doors that match the
 * specified room types.
 *
 * @returns The number of doors removed.
 * @allowEmptyVariadic
 */
function removeAllDoors(...roomTypes) {
    const doors = getDoors(...roomTypes);
    removeDoors(...doors);
    return doors.length;
}
exports.removeAllDoors = removeAllDoors;
/** Helper function to remove a single door. */
function removeDoor(door) {
    const room = cachedClasses_1.game.GetRoom();
    room.RemoveDoor(door.Slot);
}
exports.removeDoor = removeDoor;
/**
 * Helper function to remove the doors provided.
 *
 * This function is variadic, meaning that you can specify as many doors as you want to remove.
 */
function removeDoors(...doors) {
    for (const door of doors) {
        removeDoor(door);
    }
}
exports.removeDoors = removeDoors;
