UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

529 lines (528 loc) • 21.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.closeAllDoors = closeAllDoors; exports.closeDoorFast = closeDoorFast; exports.doorSlotFlagToDoorSlot = doorSlotFlagToDoorSlot; exports.doorSlotFlagsToDoorSlots = doorSlotFlagsToDoorSlots; exports.doorSlotToDirection = doorSlotToDirection; exports.doorSlotToDoorSlotFlag = doorSlotToDoorSlotFlag; exports.doorSlotsToDoorSlotFlags = doorSlotsToDoorSlotFlags; exports.getAngelRoomDoor = getAngelRoomDoor; exports.getBlueWombDoor = getBlueWombDoor; exports.getBossRushDoor = getBossRushDoor; exports.getDevilRoomDoor = getDevilRoomDoor; exports.getDevilRoomOrAngelRoomDoor = getDevilRoomOrAngelRoomDoor; exports.getDoorEnterPosition = getDoorEnterPosition; exports.getDoorSlotEnterPosition = getDoorSlotEnterPosition; exports.getDoorSlotEnterPositionOffset = getDoorSlotEnterPositionOffset; exports.getDoorSlotsForRoomShape = getDoorSlotsForRoomShape; exports.getDoors = getDoors; exports.getDoorsToRoomIndex = getDoorsToRoomIndex; exports.getMegaSatanDoor = getMegaSatanDoor; exports.getOppositeDoorSlot = getOppositeDoorSlot; exports.getRepentanceDoor = getRepentanceDoor; exports.getRoomShapeDoorSlot = getRoomShapeDoorSlot; exports.getRoomShapeDoorSlotCoordinates = getRoomShapeDoorSlotCoordinates; exports.getUnusedDoorSlots = getUnusedDoorSlots; exports.getVoidDoor = getVoidDoor; exports.hasDoorType = hasDoorType; exports.hasUnusedDoorSlot = hasUnusedDoorSlot; exports.isAngelRoomDoor = isAngelRoomDoor; exports.isBlueWombDoor = isBlueWombDoor; exports.isBossRushDoor = isBossRushDoor; exports.isDevilRoomDoor = isDevilRoomDoor; exports.isDoorSlotInRoomShape = isDoorSlotInRoomShape; exports.isDoorToDownpour = isDoorToDownpour; exports.isDoorToMausoleum = isDoorToMausoleum; exports.isDoorToMausoleumAscent = isDoorToMausoleumAscent; exports.isDoorToMines = isDoorToMines; exports.isDoorToMomsHeart = isDoorToMomsHeart; exports.isHiddenSecretRoomDoor = isHiddenSecretRoomDoor; exports.isMegaSatanDoor = isMegaSatanDoor; exports.isRepentanceDoor = isRepentanceDoor; exports.isSecretRoomDoor = isSecretRoomDoor; exports.isVoidDoor = isVoidDoor; exports.lockDoor = lockDoor; exports.openAllDoors = openAllDoors; exports.openDoorFast = openDoorFast; exports.removeAllDoors = removeAllDoors; exports.removeDoor = removeDoor; exports.removeDoors = removeDoors; 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 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); } } /** * 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); } function doorSlotFlagToDoorSlot(doorSlotFlag) { const doorSlot = doorSlotFlagToDoorSlot_1.DOOR_SLOT_FLAG_TO_DOOR_SLOT[doorSlotFlag]; return doorSlot ?? doorSlotFlagToDoorSlot_1.DEFAULT_DOOR_SLOT; } 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; } function doorSlotToDirection(doorSlot) { return doorSlotToDirection_1.DOOR_SLOT_TO_DIRECTION[doorSlot]; } function doorSlotToDoorSlotFlag(doorSlot) { return doorSlotToDoorSlotFlag_1.DOOR_SLOT_TO_DOOR_SLOT_FLAG[doorSlot]; } /** * 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); } function getAngelRoomDoor() { const angelRoomDoors = getDoors(isaac_typescript_definitions_1.RoomType.ANGEL); return angelRoomDoors.length === 0 ? undefined : angelRoomDoors[0]; } /** * 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)); } /** * 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)); } function getDevilRoomDoor() { const devilRoomDoors = getDoors(isaac_typescript_definitions_1.RoomType.DEVIL); return devilRoomDoors.length === 0 ? undefined : devilRoomDoors[0]; } /** * 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]; } /** * 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); } /** * 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); } /** * 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); } /** 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]; } /** * 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; } /** * 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)); } /** * 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)); } function getOppositeDoorSlot(doorSlot) { return oppositeDoorSlots_1.OPPOSITE_DOOR_SLOTS[doorSlot]; } /** * 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)); } /** * 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; } /** * 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]; } /** * 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); } /** * 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)); } /** * 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; } /** * 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; } function isAngelRoomDoor(door) { return door.TargetRoomType === isaac_typescript_definitions_1.RoomType.ANGEL; } /** * 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) { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.BLUE_WOMB; } /** * 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) { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.BOSS_RUSH; } function isDevilRoomDoor(door) { return door.TargetRoomType === isaac_typescript_definitions_1.RoomType.DEVIL; } /** 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); } /** * 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"; } /** * 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"; } /** * 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"; } /** * 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"; } /** * 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 } function isHiddenSecretRoomDoor(door) { const sprite = door.GetSprite(); const animation = sprite.GetAnimation(); return isSecretRoomDoor(door) && animation === "Hidden"; } /** * 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) { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.MEGA_SATAN; } /** * 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) { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.SECRET_EXIT; } /** * 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 } /** * 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) { // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison return door.TargetRoomIndex === isaac_typescript_definitions_1.GridRoom.VOID; } /** * 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); } /** * 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(); } } /** * 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); } /** * 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; } /** Helper function to remove a single door. */ function removeDoor(door) { const room = cachedClasses_1.game.GetRoom(); room.RemoveDoor(door.Slot); } /** * 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); } }