isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
200 lines (179 loc) • 6.29 kB
text/typescript
import type { PocketItemSlot } from "isaac-typescript-definitions";
import {
ActiveSlot,
CardType,
CollectibleType,
PillColor,
PlayerType,
} from "isaac-typescript-definitions";
import { POCKET_ITEM_SLOT_VALUES } from "../cachedEnumValues";
import { PocketItemType } from "../enums/PocketItemType";
import type { PocketItemDescription } from "../interfaces/PocketItemDescription";
import { isCharacter } from "./players";
/**
* Helper function to get the `PocketItemSlot` that the player's pocket active collectible item is
* in, if any. Returns undefined if the player does not have a pocket active item.
*/
export function getActivePocketItemSlot(
player: EntityPlayer,
): PocketItemSlot | undefined {
const pocketItems = getPocketItems(player);
for (const pocketItem of pocketItems) {
if (pocketItem.type === PocketItemType.ACTIVE_ITEM) {
return pocketItem.slot;
}
}
return undefined;
}
/** Helper item to get the first card that a player is holding in their pocket item slots. */
export function getFirstCard(
player: EntityPlayer,
): PocketItemDescription | undefined {
const pocketItems = getPocketItems(player);
return pocketItems.find(
(pocketItem) => pocketItem.type === PocketItemType.CARD,
);
}
/**
* Helper item to get the first card or pill that a player is holding in their pocket item slots.
*/
export function getFirstCardOrPill(
player: EntityPlayer,
): PocketItemDescription | undefined {
const pocketItems = getPocketItems(player);
return pocketItems.find(
(pocketItem) =>
pocketItem.type === PocketItemType.CARD
|| pocketItem.type === PocketItemType.PILL,
);
}
/** Helper item to get the first pill that a player is holding in their pocket item slots. */
export function getFirstPill(
player: EntityPlayer,
): PocketItemDescription | undefined {
const pocketItems = getPocketItems(player);
return pocketItems.find(
(pocketItem) => pocketItem.type === PocketItemType.PILL,
);
}
/**
* Use this helper function as a workaround for the `EntityPlayer.GetPocketItem` method not working
* correctly.
*
* Note that due to API limitations, there is no way to determine the location of a Dice Bag trinket
* dice. Furthermore, when the player has a Dice Bag trinket dice and a pocket active at the same
* time, there is no way to determine the location of the pocket active item. If this function
* cannot determine the identity of a particular slot, it will mark the type of the slot as
* `PocketItemType.UNDETERMINABLE`.
*/
export function getPocketItems(
player: EntityPlayer,
): readonly PocketItemDescription[] {
const pocketItem = player.GetActiveItem(ActiveSlot.POCKET);
const hasPocketItem = pocketItem !== CollectibleType.NULL;
const pocketItem2 = player.GetActiveItem(ActiveSlot.POCKET_SINGLE_USE);
const hasPocketItem2 = pocketItem2 !== CollectibleType.NULL;
const maxPocketItems = player.GetMaxPocketItems();
const pocketItems: PocketItemDescription[] = [];
let pocketItemIdentified = false;
let pocketItem2Identified = false;
for (const slot of POCKET_ITEM_SLOT_VALUES) {
const cardType = player.GetCard(slot);
const pillColor = player.GetPill(slot);
if (cardType !== CardType.NULL) {
pocketItems.push({
slot,
type: PocketItemType.CARD,
subType: cardType,
});
} else if (pillColor !== PillColor.NULL) {
pocketItems.push({
slot,
type: PocketItemType.PILL,
subType: pillColor,
});
} else if (hasPocketItem && !hasPocketItem2 && !pocketItemIdentified) {
pocketItemIdentified = true;
pocketItems.push({
slot,
type: PocketItemType.ACTIVE_ITEM,
subType: pocketItem,
});
} else if (!hasPocketItem && hasPocketItem2 && !pocketItem2Identified) {
pocketItem2Identified = true;
pocketItems.push({
slot,
type: PocketItemType.DICE_BAG_DICE,
subType: pocketItem2,
});
} else if (hasPocketItem && hasPocketItem2) {
pocketItems.push({
slot,
type: PocketItemType.UNDETERMINABLE,
subType: 0,
});
} else {
pocketItems.push({
slot,
type: PocketItemType.EMPTY,
subType: 0,
});
}
if (slot + 1 === maxPocketItems) {
break;
}
}
return pocketItems;
}
/**
* Returns whether the player can hold an additional pocket item, beyond what they are currently
* carrying. This takes into account items that modify the max number of pocket items, like Starter
* Deck.
*
* If the player is the Tainted Soul, this always returns false, since that character cannot pick up
* items. (Only Tainted Forgotten can pick up items.)
*/
export function hasOpenPocketItemSlot(player: EntityPlayer): boolean {
if (isCharacter(player, PlayerType.SOUL_B)) {
return false;
}
const pocketItems = getPocketItems(player);
return pocketItems.some(
(pocketItem) => pocketItem.type === PocketItemType.EMPTY,
);
}
/**
* Helper function to determine whether the player's "active" pocket item slot is set to their
* pocket active item.
*/
export function isFirstSlotPocketActiveItem(player: EntityPlayer): boolean {
const pocketItems = getPocketItems(player);
const firstPocketItem = pocketItems[0];
if (firstPocketItem === undefined) {
return false;
}
return firstPocketItem.type === PocketItemType.ACTIVE_ITEM;
}
/** Helper function to see if two sets of pocket item descriptions are identical. */
export function pocketItemsEquals(
pocketItems1: readonly PocketItemDescription[],
pocketItems2: readonly PocketItemDescription[],
): boolean {
if (pocketItems1.length !== pocketItems2.length) {
return false;
}
// eslint-disable-next-line unicorn/no-for-loop
for (let i = 0; i < pocketItems1.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const pocketItem1 = pocketItems1[i]!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const pocketItem2 = pocketItems2[i]!;
const keys = Object.keys(pocketItem1) as Array<keyof PocketItemDescription>;
for (const key of keys) {
if (pocketItem1[key] !== pocketItem2[key]) {
return false;
}
}
}
return true;
}