"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFoundSoul = exports.isChildPlayer = exports.getSubPlayerParent = exports.getPlayers = exports.getPlayerIndexVanilla = exports.getPlayerIndex = exports.getPlayerFromIndex = exports.getOtherPlayers = exports.getAllPlayers = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedClasses_1 = require("../core/cachedClasses");
const ReadonlySet_1 = require("../types/ReadonlySet");
const DEFAULT_COLLECTIBLE_TYPE = isaac_typescript_definitions_1.CollectibleType.SAD_ONION;
const EXCLUDED_CHARACTERS = new ReadonlySet_1.ReadonlySet([
    isaac_typescript_definitions_1.PlayerType.ESAU,
    isaac_typescript_definitions_1.PlayerType.SOUL_B, // 40
]);
/**
 * Helper function to get every player with no restrictions, by using `Game.GetNumPlayers` and
 * `Isaac.GetPlayer`.
 *
 * This function is almost never what you want to use. For most purposes, use the `getPlayers`
 * helper function instead to get a filtered list of players.
 */
function getAllPlayers() {
    const numPlayers = cachedClasses_1.game.GetNumPlayers();
    const players = [];
    for (let i = 0; i < numPlayers; i++) {
        const player = Isaac.GetPlayer(i);
        players.push(player);
    }
    return players;
}
exports.getAllPlayers = getAllPlayers;
/**
 * Helper function to get all of the other players in the room besides the one provided. (This
 * includes "child" players.)
 */
function getOtherPlayers(player) {
    const playerPtrHash = GetPtrHash(player);
    const players = getAllPlayers();
    return players.filter((otherPlayer) => GetPtrHash(otherPlayer) !== playerPtrHash);
}
exports.getOtherPlayers = getOtherPlayers;
/**
 * Helper function to get the corresponding `EntityPlayer` object that corresponds to a
 * `PlayerIndex`.
 */
function getPlayerFromIndex(playerIndex) {
    const players = getAllPlayers();
    return players.find((player) => getPlayerIndex(player) === playerIndex);
}
exports.getPlayerFromIndex = getPlayerFromIndex;
/**
 * Mods often have to track variables relating to the player. In naive mods, information will only
 * be stored about the first player. However, in order to be robust, mods must handle up to 4
 * players playing at the same time. This means that information must be stored on a map data
 * structure. Finding a good index for these types of map data structures is difficult:
 *
 * - We cannot use the index from `Isaac.GetPlayer(i)` since this fails in the case where there are
 *   two players and the first player leaves the run.
 * - We cannot use `EntityPlayer.ControllerIndex` as an index because it fails in the case of Jacob
 *   & Esau or Tainted Forgotten. It also fails in the case of a player changing their controls
 *   mid-run.
 * - We cannot use `EntityPlayer.GetData().index` because it does not persist across saving and
 *   continuing.
 * - We cannot use `GetPtrHash()` as an index because it does not persist across exiting and
 *   relaunching the game.
 * - We cannot use `EntityPlayer.InitSeed` because it is not consistent with additional players
 *   beyond the first.
 *
 * Instead, we use the `EntityPlayer.GetCollectibleRNG` method with an arbitrary value of Sad Onion
 * (1). This works even if the player does not have any Sad Onions.
 *
 * Note that by default, this returns the same index for both The Forgotten and The Soul. (Even
 * though they are technically different characters, they share the same inventory and `InitSeed`.)
 * If this is not desired, pass true for the `differentiateForgottenAndSoul` argument, and the RNG
 * of Spoon Bender (3) will be used for The Soul.
 *
 * Also note that this index does not work in the `POST_PLAYER_INIT` function for players 2 through
 * 4. With that said, in almost all cases, you should be lazy-initializing your data structures in
 * other callbacks, so this should not be an issue.
 */
function getPlayerIndex(player, differentiateForgottenAndSoul = false) {
    // Sub-players use separate RNG values for some reason, so we need to always use the main player:
    // https://github.com/Meowlala/RepentanceAPIIssueTracker/issues/443
    // We can safely ignore the player's character because regardless of whether the main player ends
    // up being The Forgotten or The Soul, the collectible RNG values will be the same. The
    // `EntityPlayer.IsSubPlayer` method can return true for Dead Tainted Lazarus during the
    // `POST_PLAYER_INIT` callback, but since we fall back to the player in the case of
    // "getSubPlayerParent" returning undefined, we do not need to explicitly check for this case.
    let playerToUse = player;
    const isSubPlayer = player.IsSubPlayer();
    if (isSubPlayer) {
        const subPlayer = player;
        // The "getSubPlayerParent" function will return undefined in the situation where we are on Dead
        // Tainted Lazarus in the `POST_PLAYER_INIT` callback.
        const playerParent = getSubPlayerParent(subPlayer);
        if (playerParent !== undefined) {
            playerToUse = playerParent;
        }
    }
    const collectibleType = getPlayerIndexCollectibleType(player, differentiateForgottenAndSoul);
    const collectibleRNG = playerToUse.GetCollectibleRNG(collectibleType);
    const seed = collectibleRNG.GetSeed();
    return seed;
}
exports.getPlayerIndex = getPlayerIndex;
function getPlayerIndexCollectibleType(player, differentiateForgottenAndSoul) {
    const character = player.GetPlayerType();
    if (character === isaac_typescript_definitions_1.PlayerType.SOUL) {
        return differentiateForgottenAndSoul
            ? isaac_typescript_definitions_1.CollectibleType.INNER_EYE
            : DEFAULT_COLLECTIBLE_TYPE;
    }
    return DEFAULT_COLLECTIBLE_TYPE;
}
/**
 * Helper function to return the index of this player with respect to the output of the
 * `Isaac.GetPlayer` method.
 *
 * Note that if you storing information about a player in a data structure, you never want to use
 * this index; use the `getPlayerIndex` function instead.
 */
function getPlayerIndexVanilla(playerToFind) {
    const numPlayers = cachedClasses_1.game.GetNumPlayers();
    const playerToFindHash = GetPtrHash(playerToFind);
    for (let i = 0; i < numPlayers; i++) {
        const player = Isaac.GetPlayer(i);
        const playerHash = GetPtrHash(player);
        if (playerHash === playerToFindHash) {
            return i;
        }
    }
    return undefined;
}
exports.getPlayerIndexVanilla = getPlayerIndexVanilla;
/**
 * This function always excludes players with a non-undefined parent, since they are not real
 * players (e.g. the Strawman Keeper).
 *
 * If this is not desired, use the `getAllPlayers` helper function instead.
 *
 * @param performCharacterExclusions Whether to exclude characters that are not directly controlled
 *                                 by the player (i.e. Esau & Tainted Soul). Default is false.
 */
function getPlayers(performCharacterExclusions = false) {
    const players = getAllPlayers();
    const nonChildPlayers = players.filter((player) => !isChildPlayer(player));
    const nonChildPlayersFiltered = nonChildPlayers.filter((player) => {
        const character = player.GetPlayerType();
        return !EXCLUDED_CHARACTERS.has(character);
    });
    return performCharacterExclusions ? nonChildPlayersFiltered : nonChildPlayers;
}
exports.getPlayers = getPlayers;
/**
 * Helper function to get a parent `EntityPlayer` object for a given `EntitySubPlayer` object. This
 * is useful because calling the `EntityPlayer.GetSubPlayer` method on a sub-player object will
 * return undefined.
 */
function getSubPlayerParent(subPlayer) {
    const subPlayerPtrHash = GetPtrHash(subPlayer);
    const players = getPlayers();
    return players.find((player) => {
        const thisPlayerSubPlayer = player.GetSubPlayer();
        if (thisPlayerSubPlayer === undefined) {
            return false;
        }
        const thisPlayerSubPlayerPtrHash = GetPtrHash(thisPlayerSubPlayer);
        return thisPlayerSubPlayerPtrHash === subPlayerPtrHash;
    });
}
exports.getSubPlayerParent = getSubPlayerParent;
/**
 * Helper function to detect if a particular player is a "child" player, meaning that they have a
 * non-undefined `EntityPlayer.Parent` field. (For example, the Strawman Keeper.)
 */
function isChildPlayer(player) {
    return player.Parent !== undefined;
}
exports.isChildPlayer = isChildPlayer;
/**
 * Helper function to detect if a particular player is the Found Soul player provided by the
 * trinket.
 */
function isFoundSoul(player) {
    return (isChildPlayer(player) &&
        player.Variant === isaac_typescript_definitions_1.PlayerVariant.COOP_BABY &&
        player.SubType === isaac_typescript_definitions_1.BabySubType.FOUND_SOUL);
}
exports.isFoundSoul = isFoundSoul;
