isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
473 lines (472 loc) • 19.7 kB
JavaScript
"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;
}