"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setBlindfold = exports.removeDeadEyeMultiplier = exports.isVanillaPlayer = exports.isTaintedLazarus = exports.isTainted = exports.isPlayerAbleToAim = exports.isModdedPlayer = exports.isLost = exports.isKeeper = exports.isJacobOrEsau = exports.isFirstPlayer = exports.isEden = exports.isDamageFromPlayer = exports.isCharacter = exports.isBethany = exports.hasSpectral = exports.hasPiercing = exports.hasLostCurse = exports.hasHoming = exports.hasForm = exports.getPlayersWithControllerIndex = exports.getPlayersOnKeyboard = exports.getPlayersOfType = exports.getPlayerNumHitsRemaining = exports.getPlayerName = exports.getPlayerFromPtr = exports.getPlayerFromEntity = exports.getPlayerCloserThan = exports.getNewestPlayer = exports.getFinalPlayer = exports.getClosestPlayer = exports.getCharacters = exports.getAzazelBrimstoneDistance = exports.dequeueItem = exports.canPlayerCrushRocks = exports.anyPlayerIs = exports.anyPlayerHoldingItem = void 0;
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());
}
exports.anyPlayerHoldingItem = anyPlayerHoldingItem;
/**
 * 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));
}
exports.anyPlayerIs = anyPlayerIs;
/**
 * 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));
}
exports.canPlayerCrushRocks = canPlayerCrushRocks;
/**
 * 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.
 */
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;
}
exports.dequeueItem = dequeueItem;
/**
 * 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;
}
exports.getAzazelBrimstoneDistance = getAzazelBrimstoneDistance;
/** 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());
}
exports.getCharacters = getCharacters;
/**
 * 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;
}
exports.getClosestPlayer = getClosestPlayer;
/**
 * 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;
}
exports.getFinalPlayer = getFinalPlayer;
/**
 * 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;
}
exports.getNewestPlayer = getNewestPlayer;
/**
 * 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);
}
exports.getPlayerCloserThan = getPlayerCloserThan;
/**
 * 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();
}
exports.getPlayerFromEntity = getPlayerFromEntity;
/**
 * 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();
}
exports.getPlayerFromPtr = getPlayerFromPtr;
/**
 * 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);
}
exports.getPlayerName = getPlayerName;
/**
 * 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;
}
exports.getPlayerNumHitsRemaining = getPlayerNumHitsRemaining;
/**
 * 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);
    });
}
exports.getPlayersOfType = getPlayersOfType;
/**
 * 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);
}
exports.getPlayersOnKeyboard = getPlayersOnKeyboard;
/**
 * 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);
}
exports.getPlayersWithControllerIndex = getPlayersWithControllerIndex;
/**
 * 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));
}
exports.hasForm = hasForm;
/**
 * 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);
}
exports.hasHoming = hasHoming;
/** 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);
}
exports.hasLostCurse = hasLostCurse;
/**
 * 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);
}
exports.hasPiercing = hasPiercing;
/**
 * 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);
}
exports.hasSpectral = hasSpectral;
/**
 * 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;
}
exports.isBethany = isBethany;
/**
 * 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);
}
exports.isCharacter = isCharacter;
/**
 * 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;
}
exports.isDamageFromPlayer = isDamageFromPlayer;
/**
 * 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;
}
exports.isEden = isEden;
function isFirstPlayer(player) {
    return (0, playerIndex_1.getPlayerIndexVanilla)(player) === 0;
}
exports.isFirstPlayer = isFirstPlayer;
/**
 * 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;
}
exports.isJacobOrEsau = isJacobOrEsau;
/**
 * 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;
}
exports.isKeeper = isKeeper;
/** 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;
}
exports.isLost = isLost;
function isModdedPlayer(player) {
    return !isVanillaPlayer(player);
}
exports.isModdedPlayer = isModdedPlayer;
/**
 * 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();
}
exports.isPlayerAbleToAim = isPlayerAbleToAim;
/** 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);
}
exports.isTainted = isTainted;
/** 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);
}
exports.isTaintedLazarus = isTaintedLazarus;
function isVanillaPlayer(player) {
    const character = player.GetPlayerType();
    return (0, characters_1.isVanillaCharacter)(character);
}
exports.isVanillaPlayer = isVanillaPlayer;
/**
 * 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();
    });
}
exports.removeDeadEyeMultiplier = removeDeadEyeMultiplier;
/**
 * 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);
        }
    }
}
exports.setBlindfold = setBlindfold;
/** 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;
}
