isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
158 lines (144 loc) • 5.57 kB
text/typescript
import type { ItemPoolType } from "isaac-typescript-definitions";
import { CollectibleType } from "isaac-typescript-definitions";
import { Exported } from "../../../decorators";
import { ModCallbackCustom } from "../../../enums/ModCallbackCustom";
import { arrayRemoveIndexInPlace } from "../../../functions/array";
import { copyMap } from "../../../functions/map";
import { assertDefined } from "../../../functions/utils";
import { getRandomIndexFromWeightedArray } from "../../../functions/weighted";
import { ReadonlyMap } from "../../../types/ReadonlyMap";
import type { WeightedArray } from "../../../types/WeightedArray";
import { Feature } from "../../private/Feature";
const v = {
run: {
customItemPools: new ReadonlyMap<
ItemPoolType,
WeightedArray<CollectibleType>
>(),
},
};
const customItemPoolMap = new Map<
ItemPoolType,
WeightedArray<CollectibleType>
>();
export class CustomItemPools extends Feature {
/** @internal */
public override v = v;
/** @internal */
constructor() {
super();
this.customCallbacksUsed = [
[
ModCallbackCustom.POST_GAME_STARTED_REORDERED,
this.postGameStartedReorderedFalse,
[false],
],
];
}
// ModCallbackCustom.POST_GAME_STARTED_REORDERED
// false
private readonly postGameStartedReorderedFalse = () => {
v.run.customItemPools = copyMap(customItemPoolMap);
};
/**
* Helper function to register a custom item pool. Use this function once when your mod first
* loads to declare the items that you want to be in the item pools. Then, in the middle of a run,
* you can use `getCustomItemPoolCollectible`.
*
* For example:
*
* ```ts
* const ItemPoolTypeCustom = {
* FOO = 0 as ItemPoolType,
* } as const;
*
* const FOO_ITEM_POOL = [
* [CollectibleType.SAD_ONION, 1],
* [CollectibleType.INNER_EYE, 0.5],
* ];
*
* registerCustomItemPool(ItemPoolTypeCustom.FOO, FOO_ITEM_POOL);
* ```
*
* Note that custom item pools do not currently support partial weight decrementation on sight.
*
* In order to use this function, you must upgrade your mod with `ISCFeature.CUSTOM_ITEM_POOLS`.
*
* @param itemPoolTypeCustom An integer that identifies what kind of item pool you are creating.
* It should correspond to a local `ItemPoolTypeCustom` enum in your
* mod. The integer can be any unique value and can safely overlap with
* the vanilla item pool type values (or values chosen by other mods).
* @param collectibles An array of weighted collectible tuples that represent the item pool that
* you are creating. The first element in he tuple is the `CollectibleType`,
* and the second element in the tuple is the float that represents the weight
* of the collectible.
* @public
*/
public registerCustomItemPool(
itemPoolTypeCustom: ItemPoolType,
collectibles: Readonly<WeightedArray<CollectibleType>>,
): void {
if (customItemPoolMap.has(itemPoolTypeCustom)) {
error(
`Failed to register a custom item pool since the provided type of ${itemPoolTypeCustom} was already registered.`,
);
}
customItemPoolMap.set(
itemPoolTypeCustom,
collectibles as WeightedArray<CollectibleType>,
);
}
/**
* Helper function to get a new collectible from a custom item pool created with the
* `registerCustomItemPool` function. This function has the same format as the
* `ItemPool.GetCollectible` method.
*
* By default, a collectible will not be removed from the pool once it is selected, unless the
* `decrease` argument is set to true (similar to how a vanilla item pool works).
*
* If you want to get an unseeded collectible type, you must explicitly pass `undefined` to the
* `seedOrRNG` parameter.
*
* In order to use this function, you must upgrade your mod with `ISCFeature.CUSTOM_ITEM_POOLS`.
*
* @param itemPoolTypeCustom An integer representing the custom item pool to use.
* @param seedOrRNG The `Seed` or `RNG` object to use. If an `RNG` object is provided, the
* `RNG.Next` method will be called. If `undefined` is provided, it will default
* to a random seed.
* @param decrease Optional. Whether to remove the selected collectible from the item pool.
* Default is true.
* @param defaultItem Optional. The collectible to return if the item pool is depleted. Default is
* `CollectibleType.NULL`.
* @public
*/
public getCustomItemPoolCollectible(
itemPoolTypeCustom: ItemPoolType,
seedOrRNG: Seed | RNG | undefined,
decrease = false,
defaultItem = CollectibleType.NULL,
): CollectibleType {
const customItemPool = v.run.customItemPools.get(itemPoolTypeCustom);
assertDefined(
customItemPool,
`Failed to find the custom item pool of: ${itemPoolTypeCustom}`,
);
if (customItemPool.length === 0) {
return defaultItem;
}
const randomIndex = getRandomIndexFromWeightedArray(
customItemPool,
seedOrRNG,
);
const tuple = customItemPool[randomIndex];
assertDefined(
tuple,
`Failed to get an element from a custom item pool using a random index of: ${randomIndex}`,
);
if (decrease) {
arrayRemoveIndexInPlace(customItemPool, randomIndex);
}
return tuple[0];
}
}