isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
142 lines (130 loc) • 6.15 kB
text/typescript
import type { FamiliarVariant } from "isaac-typescript-definitions";
/**
* - Converts the specified amount of tears stat into the format of `EntityPlayer.MaxFireDelay` and
* adds it to the player.
* - This function should only be used inside the `EVALUATE_CACHE` callback.
* - In this context, the "tears stat" represents what is shown on the in-game stat UI.
*
* For example:
*
* ```ts
* function evaluateCacheTears(player: EntityPlayer) {
* const numFoo = player.GetNumCollectible(CollectibleTypeCustom.FOO);
* const tearsStat = numFoo * FOO_TEARS_STAT;
* addTearsStat(player, tearsStat);
* }
* ```
*/
export function addTearsStat(player: EntityPlayer, tearsStat: float): void {
const existingTearsStat = getTearsStat(player.MaxFireDelay);
const newTearsStat = existingTearsStat + tearsStat;
const newMaxFireDelay = getFireDelay(newTearsStat);
player.MaxFireDelay = newMaxFireDelay;
}
/**
* - The `EntityPlayer` object stores a player's tear rate in the `MaxFireDelay` field. This is
* equivalent to how many tears the player can shoot per frame.
* - If you already have a "tears" stat and you want to convert it back to MaxFireDelay, then use
* this function.
* - In this context, the "tears stat" represents what is shown on the in-game stat UI.
*/
export function getFireDelay(tearsStat: float): float {
return Math.max(30 / tearsStat - 1, -0.9999);
}
/**
* - The `EntityPlayer` object stores a player's tear rate in the `MaxFireDelay` field. This is
* equivalent to how many tears the player can shoot per frame.
* - If you want to convert this to the "tears" stat that is shown on the in-game stat UI, then use
* this function.
*/
export function getTearsStat(fireDelay: float): float {
return 30 / (fireDelay + 1);
}
/**
* Helper function to check if a tear hit an enemy. A tear is considered to be missed if it hit the
* ground, a wall, or a grid entity.
*
* Note that tears are still considered to be missed if they hit a poop or fire, so you may want to
* handle those separately using the `POST_GRID_ENTITY_COLLISION` and `POST_ENTITY_COLLISION`
* callbacks, respectively.
*
* Under the hood, this function uses the `Entity.IsDead` method. (Tears will not die if they hit an
* enemy, but they will die if they hit a wall or object.)
*/
export function isMissedTear(tear: EntityTear): boolean {
return tear.IsDead();
}
/**
* Helper function to check if a given tear is from a familiar (as opposed to e.g. a player). This
* is determined by looking at the parent.
*
* For the special case of Incubus and Blood Babies, the parent of the tear is always the player,
* but the spawner entity of the tear changes. On frame 0, the spawner entity is equal to the
* player, and on frame 1, the spawner entity is equal to the familiar. For this reason, you can
* only use this function in the `POST_TEAR_INIT_VERY_LATE` callback or on frame 1+.
*
* If this function is called on frame 0, it will throw a run-time error.
*
* Note that this function does not work properly when the tear is from a Lead Pencil barrage. In
* this case, it will always appear as if the tear is coming from a player.
*
* @param tear The tear to inspect.
* @param familiarVariant Optional. Specify this to check if the tear came from a specific familiar
* variant. Default is undefined, which checks for any familiar.
* @param subType Optional. Specify this to check if the tear came from a specific familiar
* sub-type. Default is undefined, which checks for any familiar.
*/
export function isTearFromFamiliar(
tear: EntityTear,
familiarVariant?: FamiliarVariant,
subType?: int,
): boolean {
if (tear.FrameCount === 0) {
error(
'Failed to check if the given tear was from a player since the tear\'s frame count was equal to 0. (The "isTearFromFamiliar" function must only be used in the "POST_TEAR_INIT_VERY_LATE" callback or on frame 1 and onwards.)',
);
}
// Normally, all tears have a spawner entity, which is either the player or the familiar.
if (tear.SpawnerEntity === undefined) {
return false;
}
// We cannot use `tear.SpawnerType` to determine this, since it is baked in to be equal to
// `EntityType.PLAYER` regardless of whether the tear is from a player or familiar.
const familiar = tear.SpawnerEntity.ToFamiliar();
if (familiar === undefined) {
return false;
}
return (
(familiarVariant === undefined || familiarVariant === familiar.Variant)
&& (subType === undefined || subType === familiar.SubType)
);
}
/**
* Helper function to check if a given tear is from a player (as opposed to e.g. a familiar). This
* is determined by looking at the `SpawnerEntity`.
*
* For the special case of Incubus and Blood Babies, the `SpawnerEntity` of the tear is always the
* player, but the spawner entity of the tear changes. On frame 0, the spawner entity is equal to
* the player, and on frame 1, the spawner entity is equal to the familiar. For this reason, you can
* only use this function in the `POST_TEAR_INIT_VERY_LATE` callback or on frame 1+.
*
* If this function is called on frame 0, it will throw a run-time error.
*
* Note that this function does not work properly when the tear is from a Lead Pencil barrage. In
* this case, it will always appear as if the tear is coming from a player.
*/
export function isTearFromPlayer(tear: EntityTear): boolean {
if (tear.FrameCount === 0) {
error(
'Failed to check if the given tear was from a player since the tear\'s frame count was equal to 0. (The "isTearFromPlayer" function must only be used in the "POST_TEAR_INIT_VERY_LATE" callback or on frame 1 and onwards.)',
);
}
// Normally, all tears have a spawner entity, which is either the player or the familiar.
if (tear.SpawnerEntity === undefined) {
return false;
}
// We cannot use `tear.SpawnerType` to determine this, since it is baked in to be equal to
// `EntityType.PLAYER` regardless of whether the tear is from a player or familiar.
const player = tear.SpawnerEntity.ToPlayer();
return player !== undefined;
}