UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

188 lines (187 loc) • 9.78 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ItemPoolDetection = void 0; const isaac_typescript_definitions_1 = require("isaac-typescript-definitions"); const cachedClasses_1 = require("../../../core/cachedClasses"); const decorators_1 = require("../../../decorators"); const ISCFeature_1 = require("../../../enums/ISCFeature"); const collectibleTag_1 = require("../../../functions/collectibleTag"); const playerCollectibles_1 = require("../../../functions/playerCollectibles"); const playerDataStructures_1 = require("../../../functions/playerDataStructures"); const playerIndex_1 = require("../../../functions/playerIndex"); const players_1 = require("../../../functions/players"); const utils_1 = require("../../../functions/utils"); const Feature_1 = require("../../private/Feature"); const COLLECTIBLE_TYPE_THAT_IS_NOT_IN_ANY_POOLS = isaac_typescript_definitions_1.CollectibleType.KEY_PIECE_1; const COLLECTIBLES_THAT_AFFECT_ITEM_POOLS = [ isaac_typescript_definitions_1.CollectibleType.CHAOS, // 402 isaac_typescript_definitions_1.CollectibleType.SACRED_ORB, // 691 isaac_typescript_definitions_1.CollectibleType.TMTRAINER, // 721 ]; const TRINKETS_THAT_AFFECT_ITEM_POOLS = [isaac_typescript_definitions_1.TrinketType.NO]; class ItemPoolDetection extends Feature_1.Feature { moddedElementSets; /** @internal */ constructor(moddedElementSets) { super(); this.featuresUsed = [ISCFeature_1.ISCFeature.MODDED_ELEMENT_SETS]; this.moddedElementSets = moddedElementSets; } /** * Helper function to get the remaining collectibles in a given item pool. This function is * expensive, so only use it in situations where the lag is acceptable. * * In order to use this function, you must upgrade your mod with `ISCFeature.ITEM_POOL_DETECTION`. * * @public */ getCollectiblesInItemPool(itemPoolType) { const collectibleArray = this.moddedElementSets.getCollectibleTypes(); return collectibleArray.filter((collectibleType) => this.isCollectibleInItemPool(collectibleType, itemPoolType)); } /** * Helper function to see if the given collectible is still present in the given item pool. * * If the collectible is non-offensive, any Tainted Losts will be temporarily changed to Isaac and * then changed back. (This is because Tainted Lost is not able to retrieve non-offensive * collectibles from item pools). * * Under the hood, this function works by using the `ItemPool.AddRoomBlacklist` method to * blacklist every collectible except for the one provided. * * In order to use this function, you must upgrade your mod with `ISCFeature.ITEM_POOL_DETECTION`. * * @public */ isCollectibleInItemPool(collectibleType, itemPoolType) { // We use a specific collectible which is known to not be in any pools as a default value. Thus, // we must explicitly handle this case. if (collectibleType === COLLECTIBLE_TYPE_THAT_IS_NOT_IN_ANY_POOLS) { return false; } // On Tainted Lost, it is impossible to retrieve non-offensive collectibles from pools, so we // temporarily change the character to Isaac. const taintedLosts = (0, players_1.getPlayersOfType)(isaac_typescript_definitions_1.PlayerType.LOST_B); const isOffensive = (0, collectibleTag_1.collectibleHasTag)(collectibleType, isaac_typescript_definitions_1.ItemConfigTag.OFFENSIVE); let changedPlayerTypes = false; if (!isOffensive) { changedPlayerTypes = true; for (const player of taintedLosts) { player.ChangePlayerType(isaac_typescript_definitions_1.PlayerType.ISAAC); } } const { removedItemsMap, removedTrinketsMap } = removeItemsAndTrinketsThatAffectItemPools(); // Blacklist every collectible in the game except for the provided collectible. const itemPool = cachedClasses_1.game.GetItemPool(); itemPool.ResetRoomBlacklist(); for (const collectibleTypeInSet of this.moddedElementSets.getCollectibleTypes()) { if (collectibleTypeInSet !== collectibleType) { itemPool.AddRoomBlacklist(collectibleTypeInSet); } } // Get a collectible from the pool and see if it is the intended collectible. (We can use any // arbitrary value as the seed since it should not influence the result.) const seed = 1; const retrievedCollectibleType = itemPool.GetCollectible(itemPoolType, false, seed, COLLECTIBLE_TYPE_THAT_IS_NOT_IN_ANY_POOLS); const collectibleUnlocked = retrievedCollectibleType === collectibleType; // Reset the blacklist itemPool.ResetRoomBlacklist(); restoreItemsAndTrinketsThatAffectItemPools(removedItemsMap, removedTrinketsMap); // Change any players back to Tainted Lost, if necessary. if (changedPlayerTypes) { for (const player of taintedLosts) { player.ChangePlayerType(isaac_typescript_definitions_1.PlayerType.LOST_B); } } return collectibleUnlocked; } /** * Helper function to see if the given collectible is unlocked on the current save file. This * requires providing the corresponding item pool that the collectible is normally located in. * * - If any player currently has the collectible, then it is assumed to be unlocked. (This is * because in almost all cases, when a collectible is added to a player's inventory, it is * subsequently removed from all pools.) * - If the collectible is located in more than one item pool, then any item pool can be provided. * - If the collectible is not located in any item pools, then this function will always return * false. * - If the collectible is non-offensive, any Tainted Losts will be temporarily changed to Isaac * and then changed back. (This is because Tainted Lost is not able to retrieve non-offensive * collectibles from item pools). * * In order to use this function, you must upgrade your mod with `ISCFeature.ITEM_POOL_DETECTION`. * * @public */ isCollectibleUnlocked(collectibleType, itemPoolType) { if ((0, playerCollectibles_1.anyPlayerHasCollectible)(collectibleType)) { return true; } return this.isCollectibleInItemPool(collectibleType, itemPoolType); } } exports.ItemPoolDetection = ItemPoolDetection; __decorate([ decorators_1.Exported ], ItemPoolDetection.prototype, "getCollectiblesInItemPool", null); __decorate([ decorators_1.Exported ], ItemPoolDetection.prototype, "isCollectibleInItemPool", null); __decorate([ decorators_1.Exported ], ItemPoolDetection.prototype, "isCollectibleUnlocked", null); /** * Before checking the item pools, remove any collectibles or trinkets that would affect the * retrieved collectible types. */ function removeItemsAndTrinketsThatAffectItemPools() { const removedItemsMap = new Map(); const removedTrinketsMap = new Map(); for (const player of (0, playerIndex_1.getAllPlayers)()) { const removedItems = []; for (const itemToRemove of COLLECTIBLES_THAT_AFFECT_ITEM_POOLS) { // We need to include non-real collectibles (like Lilith's Incubus), so we omit the second // argument. const numCollectibles = player.GetCollectibleNum(itemToRemove); (0, utils_1.repeat)(numCollectibles, () => { player.RemoveCollectible(itemToRemove); removedItems.push(itemToRemove); }); } (0, playerDataStructures_1.mapSetPlayer)(removedItemsMap, player, removedItems); const removedTrinkets = []; for (const trinketToRemove of TRINKETS_THAT_AFFECT_ITEM_POOLS) { if (player.HasTrinket(trinketToRemove)) { const numTrinkets = player.GetTrinketMultiplier(trinketToRemove); (0, utils_1.repeat)(numTrinkets, () => { player.TryRemoveTrinket(trinketToRemove); removedTrinkets.push(trinketToRemove); }); } } (0, playerDataStructures_1.mapSetPlayer)(removedTrinketsMap, player, removedTrinkets); } return { removedItemsMap, removedTrinketsMap }; } function restoreItemsAndTrinketsThatAffectItemPools(removedItemsMap, removedTrinketsMap) { for (const player of (0, playerIndex_1.getAllPlayers)()) { const removedItems = (0, playerDataStructures_1.mapGetPlayer)(removedItemsMap, player); if (removedItems !== undefined) { for (const collectibleType of removedItems) { player.AddCollectible(collectibleType, 0, false); // Prevent Chaos from spawning pickups } } const removedTrinkets = (0, playerDataStructures_1.mapGetPlayer)(removedTrinketsMap, player); if (removedTrinkets !== undefined) { for (const trinketType of removedTrinkets) { player.AddTrinket(trinketType, false); } } } }