UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

470 lines (469 loc) • 24.5 kB
"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);