isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
1,003 lines (1,002 loc) • 45.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertXMLGridEntityType = convertXMLGridEntityType;
exports.doesGridEntityExist = doesGridEntityExist;
exports.getAllGridIndexes = getAllGridIndexes;
exports.getCollidingEntitiesWithGridEntity = getCollidingEntitiesWithGridEntity;
exports.getConstituentsFromGridEntityID = getConstituentsFromGridEntityID;
exports.getGridEntities = getGridEntities;
exports.getGridEntitiesExcept = getGridEntitiesExcept;
exports.getGridEntitiesInRadius = getGridEntitiesInRadius;
exports.getGridEntitiesMap = getGridEntitiesMap;
exports.getGridEntityANM2Path = getGridEntityANM2Path;
exports.getGridEntityCollisionPoints = getGridEntityCollisionPoints;
exports.getGridEntityID = getGridEntityID;
exports.getGridEntityIDFromConstituents = getGridEntityIDFromConstituents;
exports.getMatchingGridEntities = getMatchingGridEntities;
exports.getRockPNGPath = getRockPNGPath;
exports.getSurroundingGridEntities = getSurroundingGridEntities;
exports.getSurroundingGridIndexes = getSurroundingGridIndexes;
exports.getTopLeftWall = getTopLeftWall;
exports.getTopLeftWallGridIndex = getTopLeftWallGridIndex;
exports.isGridEntityBreakableByExplosion = isGridEntityBreakableByExplosion;
exports.isGridEntityBroken = isGridEntityBroken;
exports.isGridEntityXMLType = isGridEntityXMLType;
exports.isGridIndexAdjacentToDoor = isGridIndexAdjacentToDoor;
exports.isPoopGridEntityXMLType = isPoopGridEntityXMLType;
exports.isPostBossVoidPortal = isPostBossVoidPortal;
exports.removeAllGridEntitiesExcept = removeAllGridEntitiesExcept;
exports.removeAllMatchingGridEntities = removeAllMatchingGridEntities;
exports.removeEntitiesSpawnedFromGridEntity = removeEntitiesSpawnedFromGridEntity;
exports.removeGridEntities = removeGridEntities;
exports.removeGridEntity = removeGridEntity;
exports.setGridEntityInvisible = setGridEntityInvisible;
exports.setGridEntityType = setGridEntityType;
exports.spawnGiantPoop = spawnGiantPoop;
exports.spawnGridEntity = spawnGridEntity;
exports.spawnGridEntityWithVariant = spawnGridEntityWithVariant;
exports.spawnVoidPortal = spawnVoidPortal;
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, // 9
isaac_typescript_definitions_1.GridEntityType.PRESSURE_PLATE, // 20
isaac_typescript_definitions_1.GridEntityType.TELEPORTER, // 23
]);
const BREAKABLE_GRID_ENTITY_TYPES_BY_EXPLOSIONS = new ReadonlySet_1.ReadonlySet([
isaac_typescript_definitions_1.GridEntityType.ROCK, // 2
isaac_typescript_definitions_1.GridEntityType.ROCK_TINTED, // 4
isaac_typescript_definitions_1.GridEntityType.ROCK_BOMB, // 5
isaac_typescript_definitions_1.GridEntityType.ROCK_ALT, // 6
isaac_typescript_definitions_1.GridEntityType.SPIDER_WEB, // 10
isaac_typescript_definitions_1.GridEntityType.TNT, // 12
// GridEntityType.FIREPLACE (13) does not count since it is turned into a non-grid entity upon
// spawning.
isaac_typescript_definitions_1.GridEntityType.POOP, // 14
isaac_typescript_definitions_1.GridEntityType.ROCK_SUPER_SPECIAL, // 22
isaac_typescript_definitions_1.GridEntityType.ROCK_SPIKED, // 25
isaac_typescript_definitions_1.GridEntityType.ROCK_ALT_2, // 26
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];
}
/**
* 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));
});
}
/**
* 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);
}
/**
* 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));
}
/** 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];
}
/**
* 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);
});
}
/**
* 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);
});
}
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;
}
/**
* 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;
}
/** Helper function to get the ANM2 path for a grid entity type. */
function getGridEntityANM2Path(gridEntityType) {
const gridEntityANM2Name = getGridEntityANM2Name(gridEntityType);
return `gfx/grid/${gridEntityANM2Name}`;
}
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 };
}
/** 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}`;
}
/**
* Helper function to get a formatted string in the format returned by the `getGridEntityID`
* function.
*/
function getGridEntityIDFromConstituents(gridEntityType, variant) {
return `${gridEntityType}.${variant}`;
}
/**
* 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);
}
/**
* 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}`;
}
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;
}
/**
* 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, // Top-left
gridIndex - gridWidth, // Top
gridIndex - gridWidth + 1, // Top-right
gridIndex - 1, // Left
gridIndex + 1, // Right
gridIndex + gridWidth - 1, // Bottom-left
gridIndex + gridWidth, // Bottom
gridIndex + gridWidth + 1, // Bottom-right
];
}
/**
* 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);
}
/**
* 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;
}
/**
* 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));
}
/**
* 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;
}
/**
* 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 complete/strict-enums
}
/**
* 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;
}
/** 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);
}
/**
* 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
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
&& saveState.Variant === isaac_typescript_definitions_1.TrapdoorVariant.VOID_PORTAL
&& saveState.VarData === 1);
}
/**
* 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;
}
/**
* 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;
}
/**
* 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);
}
/**
* 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;
}
/**
* 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 =
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
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);
}
}
/**
* 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();
}
/**
* 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);
}
/**
* 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
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
&& topLeft.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_TOP_LEFT
&& topRight !== undefined
&& topRight.GetType() === isaac_typescript_definitions_1.GridEntityType.POOP
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
&& topRight.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_TOP_RIGHT
&& bottomLeft !== undefined
&& bottomLeft.GetType() === isaac_typescript_definitions_1.GridEntityType.POOP
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
&& bottomLeft.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_BOTTOM_LEFT
&& bottomRight !== undefined
&& bottomRight.GetType() === isaac_typescript_definitions_1.GridEntityType.POOP
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
&& bottomRight.GetVariant() === isaac_typescript_definitions_1.PoopGridEntityVariant.GIANT_BOTTOM_RIGHT);
}
/**
* 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);
}
/**
* 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();
// We do an explicit check to prevent run-time errors in Lua environments.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (gridIndexOrPosition === undefined) {
const gridEntityID = getGridEntityIDFromConstituents(gridEntityType, variant);
error(`Failed to spawn grid entity ${gridEntityID} since an undefined position was passed to the "spawnGridEntityWithVariant" function.`);
}
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;
}
/**
* 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;
}