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