UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

473 lines (472 loc) • 19.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.anyPlayerHoldingItem = anyPlayerHoldingItem; exports.anyPlayerIs = anyPlayerIs; exports.canPlayerCrushRocks = canPlayerCrushRocks; exports.dequeueItem = dequeueItem; exports.getAzazelBrimstoneDistance = getAzazelBrimstoneDistance; exports.getCharacters = getCharacters; exports.getClosestPlayer = getClosestPlayer; exports.getFinalPlayer = getFinalPlayer; exports.getNewestPlayer = getNewestPlayer; exports.getPlayerCloserThan = getPlayerCloserThan; exports.getPlayerFromEntity = getPlayerFromEntity; exports.getPlayerFromPtr = getPlayerFromPtr; exports.getPlayerName = getPlayerName; exports.getPlayerNumHitsRemaining = getPlayerNumHitsRemaining; exports.getPlayersOfType = getPlayersOfType; exports.getPlayersOnKeyboard = getPlayersOnKeyboard; exports.getPlayersWithControllerIndex = getPlayersWithControllerIndex; exports.hasForm = hasForm; exports.hasHoming = hasHoming; exports.hasLostCurse = hasLostCurse; exports.hasPiercing = hasPiercing; exports.hasSpectral = hasSpectral; exports.isBethany = isBethany; exports.isCharacter = isCharacter; exports.isDamageFromPlayer = isDamageFromPlayer; exports.isEden = isEden; exports.isFirstPlayer = isFirstPlayer; exports.isJacobOrEsau = isJacobOrEsau; exports.isKeeper = isKeeper; exports.isLost = isLost; exports.isModdedPlayer = isModdedPlayer; exports.isPlayerAbleToAim = isPlayerAbleToAim; exports.isTainted = isTainted; exports.isTaintedLazarus = isTaintedLazarus; exports.isVanillaPlayer = isVanillaPlayer; exports.removeDeadEyeMultiplier = removeDeadEyeMultiplier; exports.setBlindfold = setBlindfold; const isaac_typescript_definitions_1 = require("isaac-typescript-definitions"); const cachedClasses_1 = require("../core/cachedClasses"); const ReadonlySet_1 = require("../types/ReadonlySet"); const characters_1 = require("./characters"); const flag_1 = require("./flag"); const playerIndex_1 = require("./playerIndex"); const types_1 = require("./types"); const utils_1 = require("./utils"); /** * Helper function to check to see if any player is holding up an item (from e.g. an active item * activation, a poop from IBS, etc.). */ function anyPlayerHoldingItem() { const players = (0, playerIndex_1.getAllPlayers)(); return players.some((player) => player.IsHoldingItem()); } /** * Helper function to determine if the given character is present. * * This function is variadic, meaning that you can supply as many characters as you want to check * for. Returns true if any of the characters supplied are present. */ function anyPlayerIs(...matchingCharacters) { const matchingCharacterSet = new ReadonlySet_1.ReadonlySet(matchingCharacters); const characters = getCharacters(); return characters.some((character) => matchingCharacterSet.has(character)); } /** * Helper function to determine if a player will destroy a rock/pot/skull if they walk over it. * * The following situations allow for this to be true: * - the player has Leo (collectible 302) * - the player has Thunder Thighs (collectible 314) * - the player is under the effects of Mega Mush (collectible 625) * - the player has Stompy (transformation 13) */ function canPlayerCrushRocks(player) { const effects = player.GetEffects(); return (player.HasCollectible(isaac_typescript_definitions_1.CollectibleType.LEO) || player.HasCollectible(isaac_typescript_definitions_1.CollectibleType.THUNDER_THIGHS) || effects.HasCollectibleEffect(isaac_typescript_definitions_1.CollectibleType.MEGA_MUSH) || player.HasPlayerForm(isaac_typescript_definitions_1.PlayerForm.STOMPY)); } /** * Helper function to remove a collectible or trinket that is currently queued to go into a player's * inventory (i.e. the item is being held over their head). * * If the player does not have an item currently queued, then this function will be a no-op. * * Returns whether an item was actually dequeued. * * Under the hood, this clones the `QueuedItemData`, since directly setting the `Item` field to * `undefined` does not work for some reason. * * This method was discovered by im_tem. */ function dequeueItem(player) { if (player.QueuedItem.Item === undefined) { return false; } // Doing `player.QueuedItem.Item = undefined` does not work for some reason. const queue = player.QueuedItem; queue.Item = undefined; player.QueuedItem = queue; return true; } /** * Helper function to get how long Azazel's Brimstone laser should be. You can pass either an * `EntityPlayer` object or a tear height stat. * * The formula for calculating it is: 32 - 2.5 * tearHeight */ function getAzazelBrimstoneDistance(playerOrTearHeight) { const tearHeight = (0, types_1.isNumber)(playerOrTearHeight) ? playerOrTearHeight : playerOrTearHeight.TearHeight; return 32 - 2.5 * tearHeight; } /** Helper function to get an array containing the characters of all of the current players. */ function getCharacters() { const players = (0, playerIndex_1.getPlayers)(); return players.map((player) => player.GetPlayerType()); } /** * Helper function to get the closest player to a certain position. Note that this will never * include players with a non-undefined parent, since they are not real players (e.g. the Strawman * Keeper). */ function getClosestPlayer(position) { let closestPlayer; let closestDistance = Number.POSITIVE_INFINITY; for (const player of (0, playerIndex_1.getPlayers)()) { const distance = position.Distance(player.Position); if (distance < closestDistance) { closestPlayer = player; closestDistance = distance; } } (0, utils_1.assertDefined)(closestPlayer, "Failed to find the closest player."); return closestPlayer; } /** * Helper function to return the player with the highest ID, according to the `Isaac.GetPlayer` * method. */ function getFinalPlayer() { const players = (0, playerIndex_1.getPlayers)(); const lastPlayer = players.at(-1); (0, utils_1.assertDefined)(lastPlayer, "Failed to get the final player since there were 0 players."); return lastPlayer; } /** * Helper function to get the first player with the lowest frame count. Useful to find a freshly * spawned player after using items like Esau Jr. Don't use this function if two or more players * will be spawned on the same frame. */ function getNewestPlayer() { let newestPlayer; let lowestFrame = Number.POSITIVE_INFINITY; for (const player of (0, playerIndex_1.getPlayers)()) { if (player.FrameCount < lowestFrame) { newestPlayer = player; lowestFrame = player.FrameCount; } } (0, utils_1.assertDefined)(newestPlayer, "Failed to find the newest player."); return newestPlayer; } /** * Iterates over all players and checks if any are close enough to the specified position. * * @returns The first player found when iterating upwards from index 0. */ function getPlayerCloserThan(position, distance) { const players = (0, playerIndex_1.getPlayers)(); return players.find((player) => player.Position.Distance(position) <= distance); } /** * Helper function to get the player from a tear, laser, bomb, etc. Returns undefined if the entity * does not correspond to any particular player. * * This function works by looking at the `Parent` and the `SpawnerEntity` fields (in that order). As * a last resort, it will attempt to use the `Entity.ToPlayer` method on the entity itself. */ function getPlayerFromEntity(entity) { if (entity.Parent !== undefined) { const player = entity.Parent.ToPlayer(); if (player !== undefined) { return player; } const familiar = entity.Parent.ToFamiliar(); if (familiar !== undefined) { return familiar.Player; } } if (entity.SpawnerEntity !== undefined) { const player = entity.SpawnerEntity.ToPlayer(); if (player !== undefined) { return player; } const familiar = entity.SpawnerEntity.ToFamiliar(); if (familiar !== undefined) { return familiar.Player; } } return entity.ToPlayer(); } /** * Helper function to get an `EntityPlayer` object from an `EntityPtr`. Returns undefined if the * entity has gone out of scope or if the associated entity is not a player. */ function getPlayerFromPtr(entityPtr) { const entity = entityPtr.Ref; if (entity === undefined) { return undefined; } return entity.ToPlayer(); } /** * Helper function to get the proper name of the player. Use this instead of the * `EntityPlayer.GetName` method because it accounts for Blue Baby, Lazarus II, and Tainted * characters. */ function getPlayerName(player) { const character = player.GetPlayerType(); // Account for modded characters. return isModdedPlayer(player) ? player.GetName() : (0, characters_1.getCharacterName)(character); } /** * Returns the combined value of all of the player's red hearts, soul/black hearts, and bone hearts, * minus the value of the player's rotten hearts. * * This is equivalent to the number of hits that the player can currently take, but does not take * into account double damage from champion enemies and/or being on later floors. (For example, on * Womb 1, players who have 1 soul heart remaining would die in 1 hit to anything, even though this * function would report that they have 2 hits remaining.) */ function getPlayerNumHitsRemaining(player) { const hearts = player.GetHearts(); const soulHearts = player.GetSoulHearts(); const boneHearts = player.GetBoneHearts(); const eternalHearts = player.GetEternalHearts(); const rottenHearts = player.GetRottenHearts(); return hearts + soulHearts + boneHearts + eternalHearts - rottenHearts; } /** * Helper function to get all of the players that are a certain character. * * This function is variadic, meaning that you can supply as many characters as you want to check * for. Returns true if any of the characters supplied are present. */ function getPlayersOfType(...characters) { const charactersSet = new ReadonlySet_1.ReadonlySet(characters); const players = (0, playerIndex_1.getPlayers)(); return players.filter((player) => { const character = player.GetPlayerType(); return charactersSet.has(character); }); } /** * Helper function to get all of the players that are using keyboard (i.e. * `ControllerIndex.KEYBOARD`). This function returns an array of players because it is possible * that there is more than one player with the same controller index (e.g. Jacob & Esau). * * Note that this function includes players with a non-undefined parent like e.g. the Strawman * Keeper. */ function getPlayersOnKeyboard() { const players = (0, playerIndex_1.getAllPlayers)(); return players.filter((player) => player.ControllerIndex === isaac_typescript_definitions_1.ControllerIndex.KEYBOARD); } /** * Helper function to get all of the players that match the provided controller index. This function * returns an array of players because it is possible that there is more than one player with the * same controller index (e.g. Jacob & Esau). * * Note that this function includes players with a non-undefined parent like e.g. the Strawman * Keeper. */ function getPlayersWithControllerIndex(controllerIndex) { const players = (0, playerIndex_1.getAllPlayers)(); return players.filter((player) => player.ControllerIndex === controllerIndex); } /** * Helper function to check to see if a player has one or more transformations. * * This function is variadic, meaning that you can supply as many transformations as you want to * check for. Returns true if the player has any of the supplied transformations. */ function hasForm(player, ...playerForms) { return playerForms.some((playerForm) => player.HasPlayerForm(playerForm)); } /** * Helper function to check if a player has homing tears. * * Under the hood, this checks the `EntityPlayer.TearFlags` variable for `TearFlag.HOMING` (1 << 2). */ function hasHoming(player) { return (0, flag_1.hasFlag)(player.TearFlags, isaac_typescript_definitions_1.TearFlag.HOMING); } /** After touching a white fire, a player will turn into The Lost until they clear a room. */ function hasLostCurse(player) { const effects = player.GetEffects(); return effects.HasNullEffect(isaac_typescript_definitions_1.NullItemID.LOST_CURSE); } /** * Helper function to check if a player has piercing tears. * * Under the hood, this checks the `EntityPlayer.TearFlags` variable for `TearFlag.PIERCING` (1 << * 1). */ function hasPiercing(player) { return (0, flag_1.hasFlag)(player.TearFlags, isaac_typescript_definitions_1.TearFlag.PIERCING); } /** * Helper function to check if a player has spectral tears. * * Under the hood, this checks the `EntityPlayer.TearFlags` variable for `TearFlag.SPECTRAL` (1 << * 0). */ function hasSpectral(player) { return (0, flag_1.hasFlag)(player.TearFlags, isaac_typescript_definitions_1.TearFlag.SPECTRAL); } /** * Helper function for detecting when a player is Bethany or Tainted Bethany. This is useful if you * need to adjust UI elements to account for Bethany's soul charges or Tainted Bethany's blood * charges. */ function isBethany(player) { const character = player.GetPlayerType(); return character === isaac_typescript_definitions_1.PlayerType.BETHANY || character === isaac_typescript_definitions_1.PlayerType.BETHANY_B; } /** * Helper function to check if a player is a specific character (i.e. `PlayerType`). * * This function is variadic, meaning that you can supply as many characters as you want to check * for. Returns true if the player is any of the supplied characters. */ function isCharacter(player, ...characters) { const characterSet = new ReadonlySet_1.ReadonlySet(characters); const character = player.GetPlayerType(); return characterSet.has(character); } /** * Helper function to see if a damage source is from a player. Use this instead of comparing to the * entity directly because it takes familiars into account. */ function isDamageFromPlayer(damageSource) { const player = damageSource.ToPlayer(); if (player !== undefined) { return true; } const indirectPlayer = getPlayerFromEntity(damageSource); return indirectPlayer !== undefined; } /** * Helper function for detecting when a player is Eden or Tainted Eden. Useful for situations where * you want to know if the starting stats were randomized, for example. */ function isEden(player) { const character = player.GetPlayerType(); return character === isaac_typescript_definitions_1.PlayerType.EDEN || character === isaac_typescript_definitions_1.PlayerType.EDEN_B; } function isFirstPlayer(player) { return (0, playerIndex_1.getPlayerIndexVanilla)(player) === 0; } /** * Helper function for detecting when a player is Jacob or Esau. This will only match the * non-tainted versions of these characters. */ function isJacobOrEsau(player) { const character = player.GetPlayerType(); return character === isaac_typescript_definitions_1.PlayerType.JACOB || character === isaac_typescript_definitions_1.PlayerType.ESAU; } /** * Helper function for detecting when a player is Keeper or Tainted Keeper. Useful for situations * where you want to know if the health is coin hearts, for example. */ function isKeeper(player) { const character = player.GetPlayerType(); return character === isaac_typescript_definitions_1.PlayerType.KEEPER || character === isaac_typescript_definitions_1.PlayerType.KEEPER_B; } /** Helper function for detecting when a player is The Lost or Tainted Lost. */ function isLost(player) { const character = player.GetPlayerType(); return character === isaac_typescript_definitions_1.PlayerType.LOST || character === isaac_typescript_definitions_1.PlayerType.LOST_B; } function isModdedPlayer(player) { return !isVanillaPlayer(player); } /** * Helper function for determining if a player is able to turn their head by pressing the shooting * buttons. * * Under the hood, this function uses the `EntityPlayer.IsExtraAnimationFinished` method. */ function isPlayerAbleToAim(player) { return player.IsExtraAnimationFinished(); } /** Helper function for detecting if a player is one of the Tainted characters. */ function isTainted(player) { const character = player.GetPlayerType(); return isVanillaPlayer(player) ? character >= isaac_typescript_definitions_1.PlayerType.ISAAC_B : isTaintedModded(player); } /** Helper function for detecting when a player is Tainted Lazarus or Dead Tainted Lazarus. */ function isTaintedLazarus(player) { const character = player.GetPlayerType(); return (character === isaac_typescript_definitions_1.PlayerType.LAZARUS_B || character === isaac_typescript_definitions_1.PlayerType.LAZARUS_2_B); } function isVanillaPlayer(player) { const character = player.GetPlayerType(); return (0, characters_1.isVanillaCharacter)(character); } /** * Helper function to remove the Dead Eye multiplier from a player. * * Note that each time the `EntityPlayer.ClearDeadEyeCharge` method is called, it only has a chance * of working, so this function calls it 100 times to be safe. */ function removeDeadEyeMultiplier(player) { (0, utils_1.repeat)(100, () => { player.ClearDeadEyeCharge(); }); } /** * Helper function to blindfold the player by using a hack with the challenge variable. * * Note that if the player dies and respawns (from e.g. Dead Cat), the blindfold will have to be * reapplied. * * Under the hood, this function sets the challenge to one with a blindfold, changes the player to * the same character that they currently are, and then changes the challenge back. This method was * discovered by im_tem. * * @param player The player to apply or remove the blindfold state from. * @param enabled Whether to apply or remove the blindfold. * @param modifyCostume Optional. Whether to add or remove the blindfold costume. Default is true. */ function setBlindfold(player, enabled, modifyCostume = true) { const character = player.GetPlayerType(); const challenge = Isaac.GetChallenge(); if (enabled) { cachedClasses_1.game.Challenge = isaac_typescript_definitions_1.Challenge.SOLAR_SYSTEM; // This challenge has a blindfold player.ChangePlayerType(character); cachedClasses_1.game.Challenge = challenge; // The costume is applied automatically. if (!modifyCostume) { player.TryRemoveNullCostume(isaac_typescript_definitions_1.NullItemID.BLINDFOLD); } } else { cachedClasses_1.game.Challenge = isaac_typescript_definitions_1.Challenge.NULL; player.ChangePlayerType(character); cachedClasses_1.game.Challenge = challenge; if (modifyCostume) { player.TryRemoveNullCostume(isaac_typescript_definitions_1.NullItemID.BLINDFOLD); } } } /** Not exported since end-users should use the `isTainted` helper function directly. */ function isTaintedModded(player) { // This algorithm only works for modded characters because the `Isaac.GetPlayerTypeByName` method // is bugged. // https://github.com/Meowlala/RepentanceAPIIssueTracker/issues/117 const character = player.GetPlayerType(); const name = player.GetName(); const taintedCharacter = Isaac.GetPlayerTypeByName(name, true); return character === taintedCharacter; }