UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

232 lines (231 loc) • 8.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assertDefined = assertDefined; exports.assertNotNull = assertNotNull; exports.eRange = eRange; exports.getTraversalDescription = getTraversalDescription; exports.iRange = iRange; exports.inRange = inRange; exports.isMultiplayer = isMultiplayer; exports.isRepentance = isRepentance; exports.isRepentancePlus = isRepentancePlus; exports.isRepentogon = isRepentogon; exports.repeat = repeat; exports.todo = todo; const cachedClasses_1 = require("../core/cachedClasses"); const ReadonlySet_1 = require("../types/ReadonlySet"); const playerIndex_1 = require("./playerIndex"); const types_1 = require("./types"); /** * Helper function to throw an error (using the `error` Lua function) if the provided value is equal * to `undefined`. * * This is useful to have TypeScript narrow a `T | undefined` value to `T` in a concise way. */ function assertDefined(value, ...[msg]) { if (value === undefined) { error(msg); } } /** * Helper function to throw an error (using the `error` Lua function) if the provided value is equal * to `null`. * * This is useful to have TypeScript narrow a `T | null` value to `T` in a concise way. */ function assertNotNull(value, ...[msg]) { if (value === null) { error(msg); } } /** * Helper function to return an array of integers with the specified range, inclusive on the lower * end and exclusive on the high end. (The "e" in the function name stands for exclusive.) Thus, * this function works in a similar way as the built-in `range` function from Python. * * If the end is lower than the start, an empty array will be returned. * * For example: * * - `eRange(2)` returns `[0, 1]`. * - `eRange(3)` returns `[0, 1, 2]`. * - `eRange(-3)` returns `[0, -1, -2]`. * - `eRange(1, 3)` returns `[1, 2]`. * - `eRange(2, 5)` returns `[2, 3, 4]`. * - `eRange(5, 2)` returns `[]`. * - `eRange(3, 3)` returns `[]`. * * @param start The integer to start at. * @param end Optional. The integer to end at. If not specified, then the start will be 0 and the * first argument will be the end. * @param increment Optional. The increment to use. Default is 1. */ function eRange(start, end, increment = 1) { if (end === undefined) { return eRange(0, start, increment); } const array = []; for (let i = start; i < end; i += increment) { array.push(i); } return array; } /** * Helper function to log what is happening in functions that recursively move through nested data * structures. */ function getTraversalDescription(key, traversalDescription) { if (traversalDescription !== "") { traversalDescription += " --> "; } traversalDescription += tostring(key); return traversalDescription; } /** * Helper function to return an array of integers with the specified range, inclusive on both ends. * (The "i" in the function name stands for inclusive.) * * If the end is lower than the start, an empty array will be returned. * * For example: * * - `iRange(2)` returns `[0, 1, 2]`. * - `iRange(3)` returns `[0, 1, 2, 3]`. * - `iRange(-3)` returns `[0, -1, -2, -3]`. * - `iRange(1, 3)` returns `[1, 2, 3]`. * - `iRange(2, 5)` returns `[2, 3, 4, 5]`. * - `iRange(5, 2)` returns `[]`. * - `iRange(3, 3)` returns `[3]`. * * @param start The integer to start at. * @param end Optional. The integer to end at. If not specified, then the start will be 0 and the * first argument will be the end. * @param increment Optional. The increment to use. Default is 1. */ function iRange(start, end, increment = 1) { if (end === undefined) { return iRange(0, start, increment); } const exclusiveEnd = end + 1; return eRange(start, exclusiveEnd, increment); } /** * Helper function to check if a variable is within a certain range, inclusive on both ends. * * - For example, `inRange(1, 1, 3)` will return `true`. * - For example, `inRange(0, 1, 3)` will return `false`. * * @param num The number to check. * @param start The start of the range to check. * @param end The end of the range to check. */ function inRange(num, start, end) { return num >= start && num <= end; } /** * Helper function to detect if there is two or more players currently playing. * * Specifically, this function looks for unique `ControllerIndex` values across all players. * * This function is not safe to use in the `POST_PLAYER_INIT` callback, because the * `ControllerIndex` will not be set properly. As a workaround, you can use it in the * `POST_PLAYER_INIT_FIRST` callback (or some other callback like `POST_UPDATE`). */ function isMultiplayer() { const players = (0, playerIndex_1.getAllPlayers)(); const controllerIndexes = players.map((player) => player.ControllerIndex); const controllerIndexesSet = new ReadonlySet_1.ReadonlySet(controllerIndexes); return controllerIndexesSet.size > 1; } /** * Helper function to check if the player has the Repentance DLC installed. * * This function should always be used over the `REPENTANCE` constant, since the latter is not safe. * * Specifically, this function checks for the `Sprite.GetAnimation` method: * https://bindingofisaacrebirth.fandom.com/wiki/V1.06.J818#Lua_Changes */ function isRepentance() { const metatable = getmetatable(Sprite); assertDefined(metatable, "Failed to get the metatable of the Sprite global table."); const classTable = metatable.get("__class"); assertDefined(classTable, 'Failed to get the "__class" key of the Sprite metatable.'); const getAnimation = classTable.get("GetAnimation"); return (0, types_1.isFunction)(getAnimation); } /** * Helper function to check if the player has the Repentance+ DLC installed. * * This function should always be used over the `REPENTANCE_PLUS` constant, since the latter is not * safe. * * Specifically, this function checks for `Room:DamageGridWithSource` method: * https://bindingofisaacrebirth.wiki.gg/wiki/The_Binding_of_Isaac:_Repentance%2B#Modding_Changes */ function isRepentancePlus() { const room = cachedClasses_1.game.GetRoom(); const metatable = getmetatable(room); assertDefined(metatable, "Failed to get the metatable of the room class."); const damageGridWithSource = metatable.get("DamageGridWithSource"); return (0, types_1.isFunction)(damageGridWithSource); } /** * Helper function to check if the player is using REPENTOGON, an exe-hack which expands the modding * API. * * Although REPENTOGON has a `REPENTOGON` global to check if it's present, it is not safe to use as * it can be overwritten by other mods. * * Specifically, this function checks for the `Sprite.Continue` method: * https://repentogon.com/Sprite.html#continue */ function isRepentogon() { const metatable = getmetatable(Sprite); assertDefined(metatable, "Failed to get the metatable of the Sprite global table."); const classTable = metatable.get("__class"); assertDefined(classTable, 'Failed to get the "__class" key of the Sprite metatable.'); const getAnimation = classTable.get("Continue"); return (0, types_1.isFunction)(getAnimation); } /** * Helper function to repeat code N times. This is faster to type and cleaner than using a for loop. * * For example: * * ```ts * const player = Isaac.GetPlayer(); * repeat(10, () => { * player.AddCollectible(CollectibleType.STEVEN); * }); * ``` * * The repeated function is passed the index of the iteration, if needed: * * ```ts * repeat(3, (i) => { * print(i); // Prints "0", "1", "2" * }); * ``` */ function repeat(num, func) { for (let i = 0; i < num; i++) { func(i); } } /** * Helper function to signify that the enclosing code block is not yet complete. Using this function * is similar to writing a "TODO" comment, but it has the benefit of preventing ESLint errors due to * unused variables or early returns. * * When you see this function, it simply means that the programmer intends to add in more code to * this spot later. * * This function is variadic, meaning that you can pass as many arguments as you want. (This is * useful as a means to prevent unused variables.) * * This function does not actually do anything. (It is an "empty" function.) * * @allowEmptyVariadic */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function function todo(...args) { }