isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
470 lines (469 loc) • 24.5 kB
JavaScript
"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.SpawnRockAltRewards = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedClasses_1 = require("../../../core/cachedClasses");
const constants_1 = require("../../../core/constants");
const decorators_1 = require("../../../decorators");
const ISCFeature_1 = require("../../../enums/ISCFeature");
const RockAltType_1 = require("../../../enums/RockAltType");
const entitiesSpecific_1 = require("../../../functions/entitiesSpecific");
const pickupsSpecific_1 = require("../../../functions/pickupsSpecific");
const projectiles_1 = require("../../../functions/projectiles");
const random_1 = require("../../../functions/random");
const rng_1 = require("../../../functions/rng");
const spawnCollectible_1 = require("../../../functions/spawnCollectible");
const utils_1 = require("../../../functions/utils");
const vector_1 = require("../../../functions/vector");
const Feature_1 = require("../../private/Feature");
const ROCK_ALT_CHANCES = {
NOTHING: 0.68,
BASIC_DROP: 0.0967,
/** Also used for e.g. black hearts from skulls. */
TRINKET: 0.025,
COLLECTIBLE: 0.005,
};
const COIN_VELOCITY_MULTIPLIER = 2;
/** Matches the vanilla value, according to Fly's decompilation. */
const FIND_FREE_INITIAL_STEP = 70;
/** Matches the vanilla value, according to Fly's decompilation. */
const FART_RADIUS = constants_1.DISTANCE_OF_GRID_TILE * 3;
const POLYP_PROJECTILE_SPEED = 10;
const POLYP_NUM_PROJECTILES = 6;
class SpawnRockAltRewards extends Feature_1.Feature {
itemPoolDetection;
/** @internal */
constructor(itemPoolDetection) {
super();
this.featuresUsed = [ISCFeature_1.ISCFeature.ITEM_POOL_DETECTION];
this.itemPoolDetection = itemPoolDetection;
}
/**
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
* breaks.
*
* Most of the time, this function will do nothing, similar to how most of the time, when an
* individual urn is destroyed, nothing will spawn.
*
* Note that in vanilla, trinkets will not spawn if they have already been removed from the
* trinket pool. This function cannot replicate that behavior because there is no way to check to
* see if a trinket is still in the pool. Thus, it will always have a chance to spawn the
* respective trinket
* (e.g. Swallowed Penny from urns).
*
* When filled buckets are destroyed, 6 projectiles will always spawn in a random pattern (in
* addition to any other rewards that are spawned). This function does not account for this, so if
* you want to specifically emulate destroying a filled bucket, you have to account for the
* projectiles yourself.
*
* The logic in this function is based on the rewards listed on the wiki:
* https://bindingofisaacrebirth.fandom.com/wiki/Rocks
*
* If you want to spawn an unseeded reward, you must explicitly pass `undefined` to the
* `seedOrRNG` parameter.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.SPAWN_ALT_ROCK_REWARDS`.
*
* @param positionOrGridIndex The position or grid index to spawn the reward.
* @param rockAltType The type of reward to spawn. For example, `RockAltType.URN` will have a
* chance at spawning coins and spiders.
* @param seedOrRNG The `Seed` or `RNG` object to use. Normally, you should pass the `InitSeed` of
* the grid entity that was broken. If an `RNG` object is provided, the
* `RNG.Next` method will be called. If `undefined` is provided, it will default
* to a random seed.
* @returns Whether this function spawned something.
* @public
*/
spawnRockAltReward(positionOrGridIndex, rockAltType, seedOrRNG) {
const room = cachedClasses_1.game.GetRoom();
const position = (0, vector_1.isVector)(positionOrGridIndex)
? positionOrGridIndex
: room.GetGridPosition(positionOrGridIndex);
const rng = (0, rng_1.isRNG)(seedOrRNG) ? seedOrRNG : (0, rng_1.newRNG)(seedOrRNG);
switch (rockAltType) {
case RockAltType_1.RockAltType.URN: {
return this.spawnRockAltRewardUrn(position, rng);
}
case RockAltType_1.RockAltType.MUSHROOM: {
return this.spawnRockAltRewardMushroom(position, rng);
}
case RockAltType_1.RockAltType.SKULL: {
return this.spawnRockAltRewardSkull(position, rng);
}
case RockAltType_1.RockAltType.POLYP: {
return this.spawnRockAltRewardPolyp(position, rng);
}
case RockAltType_1.RockAltType.BUCKET_DOWNPOUR: {
return this.spawnRockAltRewardBucketDownpour(position, rng);
}
case RockAltType_1.RockAltType.BUCKET_DROSS: {
return this.spawnRockAltRewardBucketDross(position, rng);
}
}
}
/**
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
* breaks of `RockAltType.URN`.
*
* For more information, see the documentation for the `spawnRockAltReward` function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.SPAWN_ALT_ROCK_REWARDS`.
*/
spawnRockAltRewardUrn(position, rng) {
const room = cachedClasses_1.game.GetRoom();
const chance = (0, random_1.getRandom)(rng);
let totalChance = 0;
totalChance += ROCK_ALT_CHANCES.NOTHING;
if (chance < totalChance) {
return false;
}
totalChance += ROCK_ALT_CHANCES.BASIC_DROP;
if (chance < totalChance) {
const numCoinsChance = (0, random_1.getRandom)(rng);
const numCoins = numCoinsChance < 0.5 ? 1 : 2;
(0, utils_1.repeat)(numCoins, () => {
const randomVector = (0, vector_1.getRandomVector)(rng);
const velocity = randomVector.mul(COIN_VELOCITY_MULTIPLIER);
(0, pickupsSpecific_1.spawnCoinWithSeed)(isaac_typescript_definitions_1.CoinSubType.NULL, position, rng, velocity);
});
return true;
}
totalChance += ROCK_ALT_CHANCES.TRINKET;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnTrinketWithSeed)(isaac_typescript_definitions_1.TrinketType.SWALLOWED_PENNY, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.COLLECTIBLE;
if (chance < totalChance) {
const stillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.QUARTER, isaac_typescript_definitions_1.ItemPoolType.DEVIL);
if (stillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.QUARTER, position, rng);
return true;
}
return false;
}
// Since the detrimental effect is the final option, we don't need to check the chance.
const numEnemiesChance = (0, random_1.getRandom)(rng);
const numEnemies = numEnemiesChance < 0.5 ? 1 : 2;
(0, utils_1.repeat)(numEnemies, () => {
const targetPos = room.FindFreePickupSpawnPosition(position, FIND_FREE_INITIAL_STEP);
EntityNPC.ThrowSpider(position, undefined, targetPos, false, 0);
});
return true;
}
/**
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
* breaks of `RockAltType.MUSHROOM`.
*
* For more information, see the documentation for the `spawnRockAltReward` function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.SPAWN_ALT_ROCK_REWARDS`.
*/
spawnRockAltRewardMushroom(position, rng) {
const room = cachedClasses_1.game.GetRoom();
const roomType = room.GetType();
const chance = (0, random_1.getRandom)(rng);
let totalChance = 0;
totalChance += ROCK_ALT_CHANCES.NOTHING;
if (chance < totalChance) {
return false;
}
totalChance += ROCK_ALT_CHANCES.BASIC_DROP;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnPillWithSeed)(isaac_typescript_definitions_1.PillColor.NULL, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.TRINKET;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnTrinketWithSeed)(isaac_typescript_definitions_1.TrinketType.LIBERTY_CAP, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.COLLECTIBLE;
if (chance < totalChance) {
if (roomType === isaac_typescript_definitions_1.RoomType.SECRET) {
const wavyCapChance = (0, random_1.getRandom)(rng);
if (wavyCapChance < 0.0272) {
const stillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.WAVY_CAP, isaac_typescript_definitions_1.ItemPoolType.SECRET);
if (stillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.WAVY_CAP, position, rng);
return true;
}
}
}
const magicMushroomStillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.MAGIC_MUSHROOM, isaac_typescript_definitions_1.ItemPoolType.TREASURE);
const miniMushStillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.MINI_MUSH, isaac_typescript_definitions_1.ItemPoolType.TREASURE);
if (magicMushroomStillInPools && miniMushStillInPools) {
const collectibleChance = (0, random_1.getRandom)(rng);
const collectibleType = collectibleChance < 0.5
? isaac_typescript_definitions_1.CollectibleType.MAGIC_MUSHROOM // 12
: isaac_typescript_definitions_1.CollectibleType.MINI_MUSH; // 71
(0, spawnCollectible_1.spawnCollectible)(collectibleType, position, rng);
return true;
}
if (magicMushroomStillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.MINI_MUSH, position, rng);
return true;
}
if (miniMushStillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.MAGIC_MUSHROOM, position, rng);
return true;
}
return false;
}
// Since the detrimental effect is the final option, we don't need to check the chance.
cachedClasses_1.game.Fart(position);
cachedClasses_1.game.ButterBeanFart(position, FART_RADIUS, undefined);
return true;
}
/**
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
* breaks of `RockAltType.SKULL`.
*
* For more information, see the documentation for the `spawnRockAltReward` function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.SPAWN_ALT_ROCK_REWARDS`.
*/
spawnRockAltRewardSkull(position, rng) {
const chance = (0, random_1.getRandom)(rng);
let totalChance = 0;
totalChance += ROCK_ALT_CHANCES.NOTHING;
if (chance < totalChance) {
return false;
}
totalChance += ROCK_ALT_CHANCES.BASIC_DROP;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnCardWithSeed)(isaac_typescript_definitions_1.CardType.NULL, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.TRINKET;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnHeartWithSeed)(isaac_typescript_definitions_1.HeartSubType.BLACK, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.COLLECTIBLE;
if (chance < totalChance) {
const ghostBabyStillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.GHOST_BABY, isaac_typescript_definitions_1.ItemPoolType.TREASURE);
const dryBabyStillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.DRY_BABY, isaac_typescript_definitions_1.ItemPoolType.TREASURE);
if (ghostBabyStillInPools && dryBabyStillInPools) {
const collectibleChance = (0, random_1.getRandom)(rng);
const collectibleType = collectibleChance < 0.5
? isaac_typescript_definitions_1.CollectibleType.GHOST_BABY // 163
: isaac_typescript_definitions_1.CollectibleType.DRY_BABY; // 265
(0, spawnCollectible_1.spawnCollectible)(collectibleType, position, rng);
return true;
}
if (ghostBabyStillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.DRY_BABY, position, rng);
return true;
}
if (dryBabyStillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.GHOST_BABY, position, rng);
return true;
}
return false;
}
// Since the detrimental effect is the final option, we don't need to check the chance.
(0, entitiesSpecific_1.spawnNPCWithSeed)(isaac_typescript_definitions_1.EntityType.HOST, 0, 0, position, rng);
return true;
}
/**
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
* breaks of `RockAltType.POLYP`.
*
* For more information, see the documentation for the `spawnRockAltReward` function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.SPAWN_ALT_ROCK_REWARDS`.
*/
spawnRockAltRewardPolyp(position, rng) {
const chance = (0, random_1.getRandom)(rng);
let totalChance = 0;
totalChance += ROCK_ALT_CHANCES.NOTHING;
if (chance < totalChance) {
return false;
}
totalChance += ROCK_ALT_CHANCES.BASIC_DROP;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnHeartWithSeed)(isaac_typescript_definitions_1.HeartSubType.NULL, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.TRINKET;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnTrinketWithSeed)(isaac_typescript_definitions_1.TrinketType.UMBILICAL_CORD, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.COLLECTIBLE;
if (chance < totalChance) {
const placentaStillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.PLACENTA, isaac_typescript_definitions_1.ItemPoolType.BOSS);
const bloodClotStillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.BLOOD_CLOT, isaac_typescript_definitions_1.ItemPoolType.BOSS);
if (placentaStillInPools && bloodClotStillInPools) {
const collectibleChance = (0, random_1.getRandom)(rng);
const collectibleType = collectibleChance < 0.5
? isaac_typescript_definitions_1.CollectibleType.PLACENTA // 218
: isaac_typescript_definitions_1.CollectibleType.BLOOD_CLOT; // 254
(0, spawnCollectible_1.spawnCollectible)(collectibleType, position, rng);
return true;
}
if (placentaStillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.PLACENTA, position, rng);
return true;
}
if (bloodClotStillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.BLOOD_CLOT, position, rng);
return true;
}
return false;
}
// Since the detrimental effect is the final option, we don't need to check the chance.
(0, entitiesSpecific_1.spawnEffectWithSeed)(isaac_typescript_definitions_1.EffectVariant.CREEP_RED, 0, position, rng);
(0, projectiles_1.fireProjectilesInCircle)(undefined, position, POLYP_PROJECTILE_SPEED, POLYP_NUM_PROJECTILES);
return true;
}
/**
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
* breaks of `RockAltType.BUCKET_DOWNPOUR`.
*
* For more information, see the documentation for the `spawnRockAltReward` function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.SPAWN_ALT_ROCK_REWARDS`.
*/
spawnRockAltRewardBucketDownpour(position, rng) {
const room = cachedClasses_1.game.GetRoom();
const chance = (0, random_1.getRandom)(rng);
let totalChance = 0;
totalChance += ROCK_ALT_CHANCES.NOTHING;
if (chance < totalChance) {
return false;
}
totalChance += ROCK_ALT_CHANCES.BASIC_DROP;
if (chance < totalChance) {
const numCoinsChance = (0, random_1.getRandom)(rng);
const numCoins = numCoinsChance < 0.5 ? 1 : 2;
(0, utils_1.repeat)(numCoins, () => {
const randomVector = (0, vector_1.getRandomVector)(rng);
const velocity = randomVector.mul(COIN_VELOCITY_MULTIPLIER);
(0, pickupsSpecific_1.spawnCoinWithSeed)(isaac_typescript_definitions_1.CoinSubType.NULL, position, rng, velocity);
});
return true;
}
totalChance += ROCK_ALT_CHANCES.TRINKET;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnTrinketWithSeed)(isaac_typescript_definitions_1.TrinketType.SWALLOWED_PENNY, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.COLLECTIBLE;
if (chance < totalChance) {
const stillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.LEECH, isaac_typescript_definitions_1.ItemPoolType.TREASURE);
if (stillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.LEECH, position, rng);
return true;
}
return false;
}
// Since the detrimental effect is the final option, we don't need to check the chance.
const enemiesChance = (0, random_1.getRandom)(rng);
const entityType = enemiesChance < 0.5 ? isaac_typescript_definitions_1.EntityType.SPIDER : isaac_typescript_definitions_1.EntityType.SMALL_LEECH;
const numEnemiesChance = (0, random_1.getRandom)(rng);
const numEnemies = numEnemiesChance < 0.5 ? 1 : 2;
(0, utils_1.repeat)(numEnemies, () => {
const targetPos = room.FindFreePickupSpawnPosition(position, FIND_FREE_INITIAL_STEP);
// If the room has water, Spiders will automatically be replaced with Striders.
const spider = EntityNPC.ThrowSpider(position, undefined, targetPos, false, 0);
// There is no `ThrowLeech` function exposed in the API, so we can piggyback off of the
// `ThrowSpider` method.
if (entityType === isaac_typescript_definitions_1.EntityType.SMALL_LEECH && spider.Type !== entityType) {
spider.Morph(entityType, 0, 0, -1);
}
});
return true;
}
/**
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
* breaks of `RockAltType.BUCKET_DROSS`.
*
* For more information, see the documentation for the `spawnRockAltReward` function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.SPAWN_ALT_ROCK_REWARDS`.
*/
spawnRockAltRewardBucketDross(position, rng) {
const room = cachedClasses_1.game.GetRoom();
const chance = (0, random_1.getRandom)(rng);
let totalChance = 0;
totalChance += ROCK_ALT_CHANCES.NOTHING;
if (chance < totalChance) {
return false;
}
totalChance += ROCK_ALT_CHANCES.BASIC_DROP;
if (chance < totalChance) {
const numCoinsChance = (0, random_1.getRandom)(rng);
const numCoins = numCoinsChance < 0.5 ? 1 : 2;
(0, utils_1.repeat)(numCoins, () => {
const randomVector = (0, vector_1.getRandomVector)(rng);
const velocity = randomVector.mul(COIN_VELOCITY_MULTIPLIER);
(0, pickupsSpecific_1.spawnCoinWithSeed)(isaac_typescript_definitions_1.CoinSubType.NULL, position, rng, velocity);
});
return true;
}
totalChance += ROCK_ALT_CHANCES.TRINKET;
if (chance < totalChance) {
(0, pickupsSpecific_1.spawnTrinketWithSeed)(isaac_typescript_definitions_1.TrinketType.BUTT_PENNY, position, rng);
return true;
}
totalChance += ROCK_ALT_CHANCES.COLLECTIBLE;
if (chance < totalChance) {
const stillInPools = this.itemPoolDetection.isCollectibleInItemPool(isaac_typescript_definitions_1.CollectibleType.POOP, isaac_typescript_definitions_1.ItemPoolType.TREASURE);
if (stillInPools) {
(0, spawnCollectible_1.spawnCollectible)(isaac_typescript_definitions_1.CollectibleType.POOP, position, rng);
return true;
}
return false;
}
// Since the detrimental effect is the final option, we don't need to check the chance.
const enemiesChance = (0, random_1.getRandom)(rng);
const entityType = enemiesChance < 0.5 ? isaac_typescript_definitions_1.EntityType.DRIP : isaac_typescript_definitions_1.EntityType.SMALL_LEECH;
const numEnemiesChance = (0, random_1.getRandom)(rng);
const numEnemies = numEnemiesChance < 0.5 ? 1 : 2;
(0, utils_1.repeat)(numEnemies, () => {
const targetPos = room.FindFreePickupSpawnPosition(position, FIND_FREE_INITIAL_STEP);
const spider = EntityNPC.ThrowSpider(position, undefined, targetPos, false, 0);
// There is no `ThrowLeech` or `ThrowDrip` functions exposed in the API, so we can piggyback
// off of the `ThrowSpider` method.
spider.Morph(entityType, 0, 0, -1);
});
return true;
}
}
exports.SpawnRockAltRewards = SpawnRockAltRewards;
__decorate([
decorators_1.Exported
], SpawnRockAltRewards.prototype, "spawnRockAltReward", null);
__decorate([
decorators_1.Exported
], SpawnRockAltRewards.prototype, "spawnRockAltRewardUrn", null);
__decorate([
decorators_1.Exported
], SpawnRockAltRewards.prototype, "spawnRockAltRewardMushroom", null);
__decorate([
decorators_1.Exported
], SpawnRockAltRewards.prototype, "spawnRockAltRewardSkull", null);
__decorate([
decorators_1.Exported
], SpawnRockAltRewards.prototype, "spawnRockAltRewardPolyp", null);
__decorate([
decorators_1.Exported
], SpawnRockAltRewards.prototype, "spawnRockAltRewardBucketDownpour", null);
__decorate([
decorators_1.Exported
], SpawnRockAltRewards.prototype, "spawnRockAltRewardBucketDross", null);