UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

493 lines (492 loc) • 23.6 kB
"use strict"; /** * These functions have to do with the room grid index for the level (i.e. the position that the * room is on the grid that represents the map for the level). * * For functions having to do with the grid index inside of the room, see the "Room Grid" functions. * * @module */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getAdjacentExistingRoomGridIndexes = getAdjacentExistingRoomGridIndexes; exports.getAdjacentNonExistingRoomGridIndexes = getAdjacentNonExistingRoomGridIndexes; exports.getAdjacentRoomGridIndexes = getAdjacentRoomGridIndexes; exports.getAllRoomGridIndexes = getAllRoomGridIndexes; exports.getNewRoomCandidate = getNewRoomCandidate; exports.getNewRoomCandidatesBesideRoom = getNewRoomCandidatesBesideRoom; exports.getNewRoomCandidatesForLevel = getNewRoomCandidatesForLevel; exports.getRoomAdjacentGridIndexes = getRoomAdjacentGridIndexes; exports.getRoomDescriptorsForType = getRoomDescriptorsForType; exports.getRoomGridIndexesForType = getRoomGridIndexesForType; exports.getRoomShapeAdjacentExistingGridIndexes = getRoomShapeAdjacentExistingGridIndexes; exports.getRoomShapeAdjacentGridIndexDeltas = getRoomShapeAdjacentGridIndexDeltas; exports.getRoomShapeAdjacentGridIndexes = getRoomShapeAdjacentGridIndexes; exports.getRoomShapeAdjacentNonExistingGridIndexes = getRoomShapeAdjacentNonExistingGridIndexes; exports.inGrid = inGrid; exports.inRedKeyRoom = inRedKeyRoom; exports.isDeadEnd = isDeadEnd; exports.isDoorSlotValidAtGridIndex = isDoorSlotValidAtGridIndex; exports.isDoorSlotValidAtGridIndexForRedRoom = isDoorSlotValidAtGridIndexForRedRoom; exports.isRedKeyRoom = isRedKeyRoom; exports.isRoomInsideGrid = isRoomInsideGrid; exports.newRoom = newRoom; exports.roomExists = roomExists; exports.roomGridIndexToVector = roomGridIndexToVector; exports.vectorToRoomGridIndex = vectorToRoomGridIndex; const isaac_typescript_definitions_1 = require("isaac-typescript-definitions"); const cachedClasses_1 = require("../core/cachedClasses"); const constants_1 = require("../core/constants"); const roomShapeToDoorSlotsToGridIndexDelta_1 = require("../objects/roomShapeToDoorSlotsToGridIndexDelta"); const array_1 = require("./array"); const doors_1 = require("./doors"); const flag_1 = require("./flag"); const map_1 = require("./map"); const rng_1 = require("./rng"); const roomData_1 = require("./roomData"); const roomShape_1 = require("./roomShape"); const rooms_1 = require("./rooms"); const LEFT = -1; const UP = -constants_1.LEVEL_GRID_ROW_WIDTH; const RIGHT = 1; const DOWN = constants_1.LEVEL_GRID_ROW_WIDTH; const ADJACENT_ROOM_GRID_INDEX_DELTAS = [LEFT, UP, RIGHT, DOWN]; /** * Helper function to get only the adjacent room grid indexes that exist (i.e. have room data). * * This is just a filtering of the results of the `getAdjacentExistingRoomGridIndexes` function. See * that function for more information. * * @param roomGridIndex Optional. Default is the current room index. */ function getAdjacentExistingRoomGridIndexes(roomGridIndex) { const adjacentRoomGridIndexes = getAdjacentRoomGridIndexes(roomGridIndex); return adjacentRoomGridIndexes.filter((adjacentRoomGridIndex) => (0, roomData_1.getRoomData)(adjacentRoomGridIndex) !== undefined); } /** * Helper function to get only the adjacent room grid indexes that do not exist (i.e. do not have * room data). * * This is just a filtering of the results of the `getAdjacentExistingRoomGridIndexes` function. See * that function for more information. */ function getAdjacentNonExistingRoomGridIndexes(roomGridIndex) { const adjacentRoomGridIndexes = getAdjacentRoomGridIndexes(roomGridIndex); return adjacentRoomGridIndexes.filter((adjacentRoomGridIndex) => (0, roomData_1.getRoomData)(adjacentRoomGridIndex) === undefined); } /** * Helper function to get all of the room grid indexes that are adjacent to a given room grid index * (even if those room grid indexes do not have any rooms in them). * * Adjacent room grid indexes that are outside of the grid will not be included in the returned * array. * * If a room grid index is provided that is outside of the grid, then an empty array will be * returned. * * Note that this function does not take the shape of the room into account; it only looks at a * single room grid index. * * @param roomGridIndex Optional. Default is the current room index. */ function getAdjacentRoomGridIndexes(roomGridIndex) { const roomGridIndexToUse = roomGridIndex ?? (0, roomData_1.getRoomGridIndex)(); if (!isRoomInsideGrid(roomGridIndexToUse)) { return []; } const adjacentRoomGridIndexes = ADJACENT_ROOM_GRID_INDEX_DELTAS.map((delta) => roomGridIndexToUse + delta); return adjacentRoomGridIndexes.filter((adjacentRoomGridIndex) => isRoomInsideGrid(adjacentRoomGridIndex)); } /** * Helper function to get the room safe grid index for every room on the entire floor. This includes * off-grid rooms, such as the Devil Room. * * Rooms without any data are assumed to be non-existent and are not included. */ function getAllRoomGridIndexes() { const rooms = (0, rooms_1.getRooms)(); return rooms.map((roomDescriptor) => roomDescriptor.SafeGridIndex); } /** * Helper function to pick a random valid spot on the floor to insert a brand new room. Note that * some floors will not have any valid spots. If this is the case, this function will return * undefined. * * If you want to get an unseeded room, you must explicitly pass `undefined` to the `seedOrRNG` * parameter. * * @param seedOrRNG The `Seed` or `RNG` object to use. If an `RNG` object is provided, the * `RNG.Next` method will be called. If `undefined` is provided, it will default to * a random seed. * @param ensureDeadEnd Optional. Whether to pick a valid dead end attached to a normal room. If * false, the function will randomly pick from any valid location that would * have a red door. * @returns Either a tuple of adjacent room grid index, `DoorSlot`, and new room grid index, or * undefined. */ function getNewRoomCandidate(seedOrRNG, ensureDeadEnd = true) { const newRoomCandidatesForLevel = getNewRoomCandidatesForLevel(ensureDeadEnd); if (newRoomCandidatesForLevel.length === 0) { return undefined; } return (0, array_1.getRandomArrayElement)(newRoomCandidatesForLevel, seedOrRNG); } /** * Helper function to iterate through the possible doors for a room and see if any of them would be * a valid spot to insert a brand new room on the floor. * * @param roomGridIndex Optional. Default is the current room index. * @param ensureDeadEnd Optional. Whether to only include doors that lead to a valid dead end * attached to a normal room. If false, the function will include all doors * that would have a red door. * @returns A array of tuples of `DoorSlot` and room grid index. */ function getNewRoomCandidatesBesideRoom(roomGridIndex, ensureDeadEnd = true) { const roomDescriptor = (0, roomData_1.getRoomDescriptor)(roomGridIndex); // First, handle the case of rooms outside of the grid, which obviously cannot have any possible // adjacent new room candidates. if (!isRoomInsideGrid(roomDescriptor.SafeGridIndex)) { return []; } // Rooms without data are non-existent, so they obviously cannot have any possible adjacent new // room candidates. const roomData = roomDescriptor.Data; if (roomData === undefined) { return []; } const doorSlotToRoomGridIndexes = getRoomShapeAdjacentNonExistingGridIndexes(roomDescriptor.SafeGridIndex, roomData.Shape); const roomCandidates = []; for (const [doorSlot, adjacentRoomGridIndex] of doorSlotToRoomGridIndexes) { // The "getRoomShapeAdjacentNonExistingGridIndexes" returns grid indexes for every possible // door, but the real room we are examining will only have a subset of these doors. Thus, we // have to exclude adjacent grid indexes where it would not be possible to place a door. const doorSlotFlag = (0, doors_1.doorSlotToDoorSlotFlag)(doorSlot); if (!(0, flag_1.hasFlag)(roomData.Doors, doorSlotFlag)) { continue; } // Check to see if hypothetically creating a room at the given room grid index would be a dead // end. In other words, if we created the room, we would only want it to connect to one other // room (this one). if (ensureDeadEnd && !isDeadEnd(adjacentRoomGridIndex)) { continue; } roomCandidates.push({ doorSlot, roomGridIndex: adjacentRoomGridIndex, }); } return roomCandidates; } /** * Helper function to get all of the spots on the floor to insert a brand new room. * * @param ensureDeadEnd Optional. Whether to only include spots that are a valid dead end attached * to a normal room. If false, the function will include all valid spots that * have a red door. * @returns A array of tuples containing the adjacent room grid index, the `DoorSlot`, and the new * room grid index. */ function getNewRoomCandidatesForLevel(ensureDeadEnd = true) { // We want to iterate over every room on the floor and search for potential new room spots. const roomsInsideGrid = (0, rooms_1.getRoomsInsideGrid)(); // However, we want to filter out special rooms because they are supposed to be dead ends. const normalRooms = roomsInsideGrid.filter((room) => room.Data !== undefined && room.Data.Type === isaac_typescript_definitions_1.RoomType.DEFAULT && !(0, rooms_1.isMirrorRoom)(room.Data) // Mirror rooms do not count as special rooms. && !(0, rooms_1.isMineShaft)(room.Data)); const roomsToLookThrough = ensureDeadEnd ? normalRooms : roomsInsideGrid; const newRoomCandidates = []; for (const room of roomsToLookThrough) { const newRoomCandidatesBesideRoom = getNewRoomCandidatesBesideRoom(room.SafeGridIndex, ensureDeadEnd); for (const { doorSlot, roomGridIndex } of newRoomCandidatesBesideRoom) { newRoomCandidates.push({ adjacentRoomGridIndex: room.SafeGridIndex, doorSlot, newRoomGridIndex: roomGridIndex, }); } } return newRoomCandidates; } /** * Helper function to get the grid indexes of all the rooms connected to the given room index, * taking the shape of the room into account. (This will only include rooms with valid data.) * * Returns an empty map if the provided room grid index is out of bounds or has no associated room * data. * * @param roomGridIndex Optional. Default is the current room index. * @returns A map of `DoorSlot` to the corresponding room grid index. */ function getRoomAdjacentGridIndexes(roomGridIndex) { const roomDescriptor = (0, roomData_1.getRoomDescriptor)(roomGridIndex); if (!isRoomInsideGrid(roomDescriptor.SafeGridIndex)) { return new Map(); } const roomData = roomDescriptor.Data; if (roomData === undefined) { return new Map(); } return getRoomShapeAdjacentExistingGridIndexes(roomDescriptor.SafeGridIndex, roomData.Shape); } /** * Helper function to get an array of all of the room descriptors for rooms that match the specified * room type. * * This function only searches through rooms in the current dimension and rooms inside the grid. * * This function is variadic, meaning that you can specify N arguments to get the combined room * descriptors for N room types. */ function getRoomDescriptorsForType(...roomTypes) { const roomTypesSet = new Set(roomTypes); const roomsInsideGrid = (0, rooms_1.getRoomsInsideGrid)(); return roomsInsideGrid.filter((roomDescriptor) => roomDescriptor.Data !== undefined && roomTypesSet.has(roomDescriptor.Data.Type)); } /** * Helper function to get an array of all of the safe grid indexes for rooms that match the * specified room type. * * This function only searches through rooms in the current dimension. * * This function is variadic, meaning that you can specify N arguments to get the combined grid * indexes for N room types. */ function getRoomGridIndexesForType(...roomTypes) { const roomDescriptors = getRoomDescriptorsForType(...roomTypes); return roomDescriptors.map((roomDescriptor) => roomDescriptor.SafeGridIndex); } /** * Helper function to get only the adjacent room grid indexes for a room shape that exist (i.e. have * room data). * * This is just a filtering of the results of the `getRoomShapeAdjacentGridIndexes` function. See * that function for more information. */ function getRoomShapeAdjacentExistingGridIndexes(safeRoomGridIndex, roomShape) { const roomShapeAdjacentGridIndexes = (0, map_1.copyMap)(getRoomShapeAdjacentGridIndexes(safeRoomGridIndex, roomShape)); for (const [doorSlot, roomGridIndex] of roomShapeAdjacentGridIndexes) { const roomData = (0, roomData_1.getRoomData)(roomGridIndex); if (roomData === undefined) { roomShapeAdjacentGridIndexes.delete(doorSlot); } } return roomShapeAdjacentGridIndexes; } /** * Helper function to get the room grid index delta that each hypothetical door in a given room * shape would go to. * * This is used by the `getRoomShapeAdjacentGridIndexes` function. * * @returns A map of `DoorSlot` to the corresponding room grid index delta. */ function getRoomShapeAdjacentGridIndexDeltas(roomShape) { return roomShapeToDoorSlotsToGridIndexDelta_1.ROOM_SHAPE_TO_DOOR_SLOTS_TO_GRID_INDEX_DELTA[roomShape]; } /** * Helper function to get the room grid index that each hypothetical door in a given room shape * would go to. (This will not include room grid indexes that are outside of the grid.) * * @param safeRoomGridIndex This must be the room safe grid index (i.e. the top-left room grid index * for the respective room). * @param roomShape The shape of the hypothetical room. * @returns A map of `DoorSlot` to the corresponding room grid index. */ function getRoomShapeAdjacentGridIndexes(safeRoomGridIndex, roomShape) { const roomShapeAdjacentGridIndexDeltas = getRoomShapeAdjacentGridIndexDeltas(roomShape); const adjacentGridIndexes = new Map(); for (const [doorSlot, delta] of roomShapeAdjacentGridIndexDeltas) { const roomGridIndex = safeRoomGridIndex + delta; if (isRoomInsideGrid(roomGridIndex)) { adjacentGridIndexes.set(doorSlot, roomGridIndex); } } return adjacentGridIndexes; } /** * Helper function to get only the adjacent room grid indexes for a room shape that do not exist * (i.e. do not have room data). * * This is just a filtering of the results of the `getRoomShapeAdjacentGridIndexes` function. See * that function for more information. */ function getRoomShapeAdjacentNonExistingGridIndexes(safeRoomGridIndex, roomShape) { const roomShapeAdjacentGridIndexes = (0, map_1.copyMap)(getRoomShapeAdjacentGridIndexes(safeRoomGridIndex, roomShape)); for (const [doorSlot, roomGridIndex] of roomShapeAdjacentGridIndexes) { const roomData = (0, roomData_1.getRoomData)(roomGridIndex); if (roomData !== undefined) { roomShapeAdjacentGridIndexes.delete(doorSlot); } } return roomShapeAdjacentGridIndexes; } /** * Helper function to determine if the current room grid index is inside of the normal 13x13 level * grid. * * For example, Devil Rooms and the Mega Satan room are not considered to be inside the grid. */ function inGrid() { const roomGridIndex = (0, roomData_1.getRoomGridIndex)(); return isRoomInsideGrid(roomGridIndex); } /** * Helper function to detect if the current room was created by the Red Key item. * * Under the hood, this checks for the `RoomDescriptorFlag.FLAG_RED_ROOM` flag. */ function inRedKeyRoom() { const roomGridIndex = (0, roomData_1.getRoomGridIndex)(); return isRedKeyRoom(roomGridIndex); } /** * Helper function to check if the given room grid index is a dead end. Specifically, this is * defined as having only one adjacent room that exists. * * Note that this function does not take the shape of the room into account; it only looks at a * single room grid index. * * This function does not care if the given room grid index actually exists, so you can use it to * check if a hypothetical room would be a dead end. * * @param roomGridIndex Optional. Default is the current room index. */ function isDeadEnd(roomGridIndex) { const adjacentExistingRoomGridIndexes = getAdjacentExistingRoomGridIndexes(roomGridIndex); return adjacentExistingRoomGridIndexes.length === 1; } function isDoorSlotValidAtGridIndex(doorSlot, roomGridIndex) { const allowedDoors = (0, roomData_1.getRoomAllowedDoors)(roomGridIndex); return allowedDoors.has(doorSlot); } function isDoorSlotValidAtGridIndexForRedRoom(doorSlot, roomGridIndex) { const doorSlotValidAtGridIndex = isDoorSlotValidAtGridIndex(doorSlot, roomGridIndex); if (!doorSlotValidAtGridIndex) { return false; } const roomShape = (0, roomData_1.getRoomShape)(roomGridIndex); if (roomShape === undefined) { return false; } const delta = (0, roomShape_1.getGridIndexDelta)(roomShape, doorSlot); if (delta === undefined) { return false; } const redRoomGridIndex = roomGridIndex + delta; return !roomExists(redRoomGridIndex) && isRoomInsideGrid(redRoomGridIndex); } /** * Helper function to detect if the provided room was created by the Red Key item. * * Under the hood, this checks for the `RoomDescriptorFlag.FLAG_RED_ROOM` flag. * * @param roomGridIndex Optional. Default is the current room index. */ function isRedKeyRoom(roomGridIndex) { const roomDescriptor = (0, roomData_1.getRoomDescriptor)(roomGridIndex); return (0, flag_1.hasFlag)(roomDescriptor.Flags, isaac_typescript_definitions_1.RoomDescriptorFlag.RED_ROOM); } /** * Helper function to determine if a given room grid index is inside of the normal 13x13 level grid. * * For example, Devil Rooms and the Mega Satan room are not considered to be inside the grid. * * @param roomGridIndex Optional. Default is the current room index. */ function isRoomInsideGrid(roomGridIndex) { roomGridIndex ??= (0, roomData_1.getRoomGridIndex)(); return roomGridIndex >= 0 && roomGridIndex <= constants_1.MAX_LEVEL_GRID_INDEX; } /** * Helper function to generate a new room on the floor. * * If you want to generate an unseeded room, you must explicitly pass `undefined` to the `seedOrRNG` * parameter. * * Under the hood, this function uses the `Level.MakeRedRoomDoor` method to create the room. * * @param seedOrRNG The `Seed` or `RNG` object to use. If an `RNG` object is provided, the * `RNG.Next` method will be called. Default is `Level.GetDungeonPlacementSeed`. * Note that the RNG is only used to select the random location to put the room on * the floor; it does not influence the randomly chosen room contents. (That is * performed by the game and can not be manipulated prior to its generation.) * @param ensureDeadEnd Optional. Whether to place the room at a valid dead end attached to a normal * room. If false, it will randomly appear at any valid location that would * have a red door. * @param customRoomData Optional. By default, the newly created room will have data corresponding * to the game's randomly generated red room. If you provide this function * with room data, it will be used to override the vanilla data. * @returns The room grid index of the new room or undefined if the floor had no valid dead ends to * place a room. */ function newRoom(seedOrRNG, ensureDeadEnd = true, customRoomData) { const level = cachedClasses_1.game.GetLevel(); seedOrRNG ??= level.GetDungeonPlacementSeed(); const rng = (0, rng_1.isRNG)(seedOrRNG) ? seedOrRNG : (0, rng_1.newRNG)(seedOrRNG); const newRoomCandidate = getNewRoomCandidate(rng, ensureDeadEnd); if (newRoomCandidate === undefined) { return undefined; } const { adjacentRoomGridIndex, doorSlot, newRoomGridIndex } = newRoomCandidate; level.MakeRedRoomDoor(adjacentRoomGridIndex, doorSlot); // By default, the room will be a "red room" and have a red graphical tint, so we want to make it // a normal room. const roomDescriptor = (0, roomData_1.getRoomDescriptor)(newRoomGridIndex); roomDescriptor.Flags = (0, flag_1.removeFlag)(roomDescriptor.Flags, isaac_typescript_definitions_1.RoomDescriptorFlag.RED_ROOM); if (customRoomData !== undefined) { roomDescriptor.Data = customRoomData; } // By default, the new room will not appear on the map, even if the player has The Mind. Thus, we // must manually alter the `DisplayFlags` of the room descriptor. const roomData = roomDescriptor.Data; if (roomData !== undefined) { const hasFullMap = level.GetStateFlag(isaac_typescript_definitions_1.LevelStateFlag.FULL_MAP_EFFECT); const hasCompass = level.GetStateFlag(isaac_typescript_definitions_1.LevelStateFlag.COMPASS_EFFECT); const hasBlueMap = level.GetStateFlag(isaac_typescript_definitions_1.LevelStateFlag.BLUE_MAP_EFFECT); const roomType = roomData.Type; const isSecretRoom = (0, rooms_1.isSecretRoomType)(roomType); if (hasFullMap) { roomDescriptor.DisplayFlags = constants_1.ALL_DISPLAY_FLAGS; } else if (!isSecretRoom && hasCompass) { roomDescriptor.DisplayFlags = (0, flag_1.addFlag)(isaac_typescript_definitions_1.DisplayFlag.VISIBLE, isaac_typescript_definitions_1.DisplayFlag.SHOW_ICON); } else if (isSecretRoom && hasBlueMap) { roomDescriptor.DisplayFlags = (0, flag_1.addFlag)(isaac_typescript_definitions_1.DisplayFlag.VISIBLE, isaac_typescript_definitions_1.DisplayFlag.SHOW_ICON); } } return newRoomGridIndex; } /** * Helper function to check if a room exists at the given room grid index. (A room will exist if it * has non-undefined data in the room descriptor.) */ function roomExists(roomGridIndex) { const roomData = (0, roomData_1.getRoomData)(roomGridIndex); return roomData !== undefined; } /** * Helper function to get the coordinates of a given room grid index. The floor is represented by a * 13x13 grid. * * - Since the starting room is in the center, the starting room grid index of 84 is equal to * coordinates of (6, 6). * - The top-left grid index of 0 is equal to coordinates of: (12, 0) * - The top-right grid index of 12 is equal to coordinates of: (0, 0) * - The bottom-left grid index of 156 is equal to coordinates of: (0, 12) * - The bottom-right grid index of 168 is equal to coordinates of: (12, 12) */ function roomGridIndexToVector(roomGridIndex) { const x = roomGridIndex % constants_1.LEVEL_GRID_ROW_WIDTH; const y = Math.floor(roomGridIndex / constants_1.LEVEL_GRID_ROW_WIDTH); return Vector(x, y); } /** * Helper function to convert a room grid index expressed as a vector back into an integer. * * Also see the `roomGridIndexToVector` helper function. */ function vectorToRoomGridIndex(roomVector) { return roomVector.Y * constants_1.LEVEL_GRID_ROW_WIDTH + roomVector.X; }