UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

532 lines (531 loc) • 24.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateStageType = calculateStageType; exports.calculateStageTypeRepentance = calculateStageTypeRepentance; exports.getEffectiveStage = getEffectiveStage; exports.getGotoCommand = getGotoCommand; exports.getLevelName = getLevelName; exports.getStage = getStage; exports.getStageID = getStageID; exports.getStageIDName = getStageIDName; exports.getStageType = getStageType; exports.getStageTypeSuffix = getStageTypeSuffix; exports.isFinalFloor = isFinalFloor; exports.isRepentanceStage = isRepentanceStage; exports.isStageWithNaturalDevilRoom = isStageWithNaturalDevilRoom; exports.isStageWithRandomBossCollectible = isStageWithRandomBossCollectible; exports.isStageWithSecretExitToDownpour = isStageWithSecretExitToDownpour; exports.isStageWithSecretExitToMausoleum = isStageWithSecretExitToMausoleum; exports.isStageWithSecretExitToMines = isStageWithSecretExitToMines; exports.isStageWithShovelTrapdoors = isStageWithShovelTrapdoors; exports.isStageWithStoryBoss = isStageWithStoryBoss; exports.onAscent = onAscent; exports.onCathedral = onCathedral; exports.onChest = onChest; exports.onDarkRoom = onDarkRoom; exports.onEffectiveStage = onEffectiveStage; exports.onFinalFloor = onFinalFloor; exports.onFirstFloor = onFirstFloor; exports.onRepentanceStage = onRepentanceStage; exports.onSheol = onSheol; exports.onStage = onStage; exports.onStageOrHigher = onStageOrHigher; exports.onStageOrLower = onStageOrLower; exports.onStageType = onStageType; exports.onStageWithNaturalDevilRoom = onStageWithNaturalDevilRoom; exports.onStageWithRandomBossCollectible = onStageWithRandomBossCollectible; exports.onStageWithSecretExitToDownpour = onStageWithSecretExitToDownpour; exports.onStageWithSecretExitToMausoleum = onStageWithSecretExitToMausoleum; exports.onStageWithSecretExitToMines = onStageWithSecretExitToMines; exports.onStageWithShovelTrapdoors = onStageWithShovelTrapdoors; exports.onStageWithStoryBoss = onStageWithStoryBoss; exports.setStage = setStage; const isaac_typescript_definitions_1 = require("isaac-typescript-definitions"); const cachedClasses_1 = require("../core/cachedClasses"); const roomTypeSpecialGotoPrefixes_1 = require("../objects/roomTypeSpecialGotoPrefixes"); const stageIDNames_1 = require("../objects/stageIDNames"); const stageToStageID_1 = require("../objects/stageToStageID"); const stageTypeSuffixes_1 = require("../objects/stageTypeSuffixes"); const log_1 = require("./log"); const types_1 = require("./types"); const utils_1 = require("./utils"); /** * Helper function that calculates what the stage type should be for the provided stage. This * emulates what the game's internal code does. */ function calculateStageType(stage) { // The following is the game's internal code to determine the floor type. (This came directly from // Spider.) /* u32 Seed = g_Game->GetSeeds().GetStageSeed(NextStage); if (!g_Game->IsGreedMode()) { StageType = ((Seed % 2) == 0 && ( ((NextStage == STAGE1_1 || NextStage == STAGE1_2) && gd.Unlocked(ACHIEVEMENT_CELLAR)) || ((NextStage == STAGE2_1 || NextStage == STAGE2_2) && gd.Unlocked(ACHIEVEMENT_CATACOMBS)) || ((NextStage == STAGE3_1 || NextStage == STAGE3_2) && gd.Unlocked(ACHIEVEMENT_NECROPOLIS)) || ((NextStage == STAGE4_1 || NextStage == STAGE4_2))) ) ? STAGE_TYPE_WOTL : STAGE_TYPE_ORIGINAL; if (Seed % 3 == 0 && NextStage < STAGE5) StageType = STAGE_TYPE_AFTERBIRTH; */ const seeds = cachedClasses_1.game.GetSeeds(); const stageSeed = seeds.GetStageSeed(stage); if (stageSeed % 2 === 0) { return isaac_typescript_definitions_1.StageType.WRATH_OF_THE_LAMB; } if (stageSeed % 3 === 0) { return isaac_typescript_definitions_1.StageType.AFTERBIRTH; } return isaac_typescript_definitions_1.StageType.ORIGINAL; } /** * Helper function that calculates what the Repentance stage type should be for the provided stage. * This emulates what the game's internal code does. */ function calculateStageTypeRepentance(stage) { // There is no alternate floor for Corpse. if (stage === isaac_typescript_definitions_1.LevelStage.WOMB_1 || stage === isaac_typescript_definitions_1.LevelStage.WOMB_2) { return isaac_typescript_definitions_1.StageType.REPENTANCE; } // This algorithm is from Kilburn. We add one because the alt path is offset by 1 relative to the // normal path. const seeds = cachedClasses_1.game.GetSeeds(); const adjustedStage = (0, types_1.asLevelStage)(stage + 1); const stageSeed = seeds.GetStageSeed(adjustedStage); // Kilburn does not know why he divided the stage seed by 2 first. const halfStageSeed = Math.floor(stageSeed / 2); if (halfStageSeed % 2 === 0) { return isaac_typescript_definitions_1.StageType.REPENTANCE_B; } return isaac_typescript_definitions_1.StageType.REPENTANCE; } /** * Helper function to account for Repentance floors being offset by 1. For example, Downpour 2 is * the third level of the run, but the game considers it to have a stage of 2. This function will * consider Downpour 2 to have a stage of 3. */ function getEffectiveStage() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); if (onRepentanceStage()) { return stage + 1; } return stage; } /** * Helper function to get the corresponding "goto" console command that would correspond to the * provided room type and room variant. * * @param roomType The `RoomType` of the destination room. * @param roomVariant The variant of the destination room. * @param useSpecialRoomsForRoomTypeDefault Optional. Whether to use `s.default` as the prefix for * the `goto` command (instead of `d`) if the room type is * `RoomType.DEFAULT` (1). False by default. */ function getGotoCommand(roomType, roomVariant, useSpecialRoomsForRoomTypeDefault = false) { const isNormalRoom = roomType === isaac_typescript_definitions_1.RoomType.DEFAULT && !useSpecialRoomsForRoomTypeDefault; const roomTypeSpecialGotoPrefix = roomTypeSpecialGotoPrefixes_1.ROOM_TYPE_SPECIAL_GOTO_PREFIXES[roomType]; const prefix = isNormalRoom ? "d" : `s.${roomTypeSpecialGotoPrefix}`; return `goto ${prefix}.${roomVariant}`; } /** * Helper function to get the English name of the level. For example, "Caves 1". * * This is useful because the `Level.GetName` method returns a localized version of the level name, * which will not display correctly on some fonts. * * Note that this returns "Blue Womb" instead of "???" for stage 9. * * @param stage Optional. If not specified, the current stage will be used. * @param stageType Optional. If not specified, the current stage type will be used. */ function getLevelName(stage, stageType) { const level = cachedClasses_1.game.GetLevel(); stage ??= level.GetStage(); stageType ??= level.GetStageType(); const stageID = getStageID(stage, stageType); const stageIDName = getStageIDName(stageID); let suffix; switch (stage) { case isaac_typescript_definitions_1.LevelStage.BASEMENT_1: case isaac_typescript_definitions_1.LevelStage.CAVES_1: case isaac_typescript_definitions_1.LevelStage.DEPTHS_1: case isaac_typescript_definitions_1.LevelStage.WOMB_1: { suffix = " 1"; break; } case isaac_typescript_definitions_1.LevelStage.BASEMENT_2: case isaac_typescript_definitions_1.LevelStage.CAVES_2: case isaac_typescript_definitions_1.LevelStage.DEPTHS_2: case isaac_typescript_definitions_1.LevelStage.WOMB_2: { suffix = " 2"; break; } default: { suffix = ""; break; } } return stageIDName + suffix; } /** Alias for the `Level.GetStage` method. */ function getStage() { const level = cachedClasses_1.game.GetLevel(); return level.GetStage(); } /** * Helper function to get the stage ID that corresponds to a particular stage and stage type. * * This is useful because `getRoomStageID` will not correctly return the `StageID` if the player is * in a special room. * * This correctly handles the case of Greed Mode. In Greed Mode, if an undefined stage and stage * type combination are passed, `StageID.SPECIAL_ROOMS` (0) will be returned. * * @param stage Optional. If not specified, the stage corresponding to the current floor will be * used. * @param stageType Optional. If not specified, the stage type corresponding to the current floor * will be used. */ function getStageID(stage, stageType) { const level = cachedClasses_1.game.GetLevel(); stage ??= level.GetStage(); stageType ??= level.GetStageType(); if (cachedClasses_1.game.IsGreedMode()) { const stageTypeToStageID = stageToStageID_1.STAGE_TO_STAGE_ID_GREED_MODE.get(stage); if (stageTypeToStageID === undefined) { return isaac_typescript_definitions_1.StageID.SPECIAL_ROOMS; } return stageTypeToStageID[stageType]; } const stageTypeToStageID = stageToStageID_1.STAGE_TO_STAGE_ID[stage]; return stageTypeToStageID[stageType]; } /** * Helper function to get the English name corresponding to a stage ID. For example, "Caves". * * This is derived from the data in the "stages.xml" file. * * Note that unlike "stages.xml", Blue Womb is specified with a name of "Blue Womb" instead of * "???". */ function getStageIDName(stageID) { return stageIDNames_1.STAGE_ID_NAMES[stageID]; } /** Alias for the `Level.GetStageType` method. */ function getStageType() { const level = cachedClasses_1.game.GetLevel(); return level.GetStageType(); } /** * Helper function to convert a numerical `StageType` into the letter suffix supplied to the "stage" * console command. For example, `StageType.REPENTANCE` is the stage type for Downpour, and the * console command to go to Downpour is "stage 1c", so this function converts `StageType.REPENTANCE` * to "c". */ function getStageTypeSuffix(stageType) { return stageTypeSuffixes_1.STAGE_TYPE_SUFFIXES[stageType]; } /** * Returns whether the provided stage and stage type represent a "final floor". This is defined as a * floor that prevents the player from entering the I AM ERROR room on. * * For example, when using Undefined on The Chest, it has a 50% chance of teleporting the player to * the Secret Room and a 50% chance of teleporting the player to the Super Secret Room, because the * I AM ERROR room is never entered into the list of possibilities. */ function isFinalFloor(stage, stageType) { return (stage === isaac_typescript_definitions_1.LevelStage.DARK_ROOM_CHEST || stage === isaac_typescript_definitions_1.LevelStage.VOID || stage === isaac_typescript_definitions_1.LevelStage.HOME || (stage === isaac_typescript_definitions_1.LevelStage.WOMB_2 && isRepentanceStage(stageType)) // Corpse 2 ); } /** * Helper function to check if the provided stage type is equal to `StageType.REPENTANCE` or * `StageType.REPENTANCE_B`. */ function isRepentanceStage(stageType) { return (stageType === isaac_typescript_definitions_1.StageType.REPENTANCE || stageType === isaac_typescript_definitions_1.StageType.REPENTANCE_B); } /** * Helper function to check if the provided effective stage is one that has the possibility to grant * a natural Devil Room or Angel Room after killing the boss. * * Note that in order for this function to work properly, you must provide it with the effective * stage (e.g. from the `getEffectiveStage` helper function) and not the absolute stage (e.g. from * the `Level.GetStage` method). */ function isStageWithNaturalDevilRoom(effectiveStage) { return ((0, utils_1.inRange)(effectiveStage, isaac_typescript_definitions_1.LevelStage.BASEMENT_2, isaac_typescript_definitions_1.LevelStage.WOMB_2) && effectiveStage !== isaac_typescript_definitions_1.LevelStage.BLUE_WOMB); } /** * Helper function to check if the provided stage is one that will have a random collectible drop * upon defeating the boss of the floor. * * This happens on most stages but will not happen on Depths 2, Womb 2, Sheol, Cathedral, Dark Room, * The Chest, and Home (due to the presence of a story boss). * * Note that even though Delirium does not drop a random boss collectible, The Void is still * considered to be a stage that has a random boss collectible since all of the non-Delirium Boss * Rooms will drop random boss collectibles. */ function isStageWithRandomBossCollectible(stage) { return !isStageWithStoryBoss(stage) || stage === isaac_typescript_definitions_1.LevelStage.VOID; } /** * Helper function to check if the provided stage will spawn a locked door to Downpour/Dross after * defeating the boss. */ function isStageWithSecretExitToDownpour(stage) { return stage === isaac_typescript_definitions_1.LevelStage.BASEMENT_1 || stage === isaac_typescript_definitions_1.LevelStage.BASEMENT_2; } /** * Helper function to check if the provided stage and stage type will spawn a spiked door to * Mausoleum/Gehenna after defeating the boss. */ function isStageWithSecretExitToMausoleum(stage, stageType) { const repentanceStage = isRepentanceStage(stageType); return ((stage === isaac_typescript_definitions_1.LevelStage.DEPTHS_1 && !repentanceStage) || (stage === isaac_typescript_definitions_1.LevelStage.CAVES_2 && repentanceStage)); } /** * Helper function to check if the provided stage and stage type will spawn a wooden door to * Mines/Ashpit after defeating the boss. */ function isStageWithSecretExitToMines(stage, stageType) { const repentanceStage = isRepentanceStage(stageType); return ((stage === isaac_typescript_definitions_1.LevelStage.CAVES_1 && !repentanceStage) || (stage === isaac_typescript_definitions_1.LevelStage.BASEMENT_2 && repentanceStage)); } /** * Helper function to check if the current stage is one that would create a trapdoor if We Need to * Go Deeper was used. */ function isStageWithShovelTrapdoors(stage, stageType) { const repentanceStage = isRepentanceStage(stageType); return (stage < isaac_typescript_definitions_1.LevelStage.WOMB_2 || (stage === isaac_typescript_definitions_1.LevelStage.WOMB_2 && !repentanceStage)); } /** * Helper function to check if the provided stage is one with a story boss. Specifically, this is * Depths 2 (Mom), Womb 2 (Mom's Heart / It Lives), Blue Womb (Hush), Sheol (Satan), Cathedral * (Isaac), Dark Room (Lamb), The Chest (Blue Baby), The Void (Delirium), and Home (Dogma / The * Beast). */ function isStageWithStoryBoss(stage) { return stage === isaac_typescript_definitions_1.LevelStage.DEPTHS_2 || stage >= isaac_typescript_definitions_1.LevelStage.WOMB_2; } /** * Helper function to check if the player has taken Dad's Note. This sets the game state flag of * `GameStateFlag.BACKWARDS_PATH` and causes floor generation to change. */ function onAscent() { return cachedClasses_1.game.GetStateFlag(isaac_typescript_definitions_1.GameStateFlag.BACKWARDS_PATH); } function onCathedral() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return (stage === isaac_typescript_definitions_1.LevelStage.SHEOL_CATHEDRAL && stageType === isaac_typescript_definitions_1.StageType.WRATH_OF_THE_LAMB); } function onChest() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return (stage === isaac_typescript_definitions_1.LevelStage.DARK_ROOM_CHEST && stageType === isaac_typescript_definitions_1.StageType.WRATH_OF_THE_LAMB); } function onDarkRoom() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return (stage === isaac_typescript_definitions_1.LevelStage.DARK_ROOM_CHEST && stageType === isaac_typescript_definitions_1.StageType.ORIGINAL); } /** * Helper function to check if the current stage matches one of the given stages. This uses the * `getEffectiveStage` helper function so that the Repentance floors are correctly adjusted. * * This function is variadic, which means you can pass as many stages as you want to match for. */ function onEffectiveStage(...effectiveStages) { const thisEffectiveStage = getEffectiveStage(); return effectiveStages.includes(thisEffectiveStage); } /** * Returns whether the player is on the "final floor" of the particular run. The final floor is * defined as one that prevents the player from entering the I AM ERROR room on. * * For example, when using Undefined on The Chest, it has a 50% chance of teleporting the player to * the Secret Room and a 50% chance of teleporting the player to the Super Secret Room, because the * I AM ERROR room is never entered into the list of possibilities. */ function onFinalFloor() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return isFinalFloor(stage, stageType); } /** * Returns whether the player is on the first floor of the particular run. * * This is tricky to determine because we have to handle the cases of Downpour/Dross 1 not being the * first floor and The Ascent. */ function onFirstFloor() { const effectiveStage = getEffectiveStage(); const isOnAscent = onAscent(); return effectiveStage === isaac_typescript_definitions_1.LevelStage.BASEMENT_1 && !isOnAscent; } /** * Helper function to check if the current stage type is equal to `StageType.REPENTANCE` or * `StageType.REPENTANCE_B`. */ function onRepentanceStage() { const level = cachedClasses_1.game.GetLevel(); const stageType = level.GetStageType(); return isRepentanceStage(stageType); } function onSheol() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return (stage === isaac_typescript_definitions_1.LevelStage.SHEOL_CATHEDRAL && stageType === isaac_typescript_definitions_1.StageType.ORIGINAL); } /** * Helper function to check if the current stage matches one of the given stages. * * This function is variadic, which means you can pass as many stages as you want to match for. */ function onStage(...stages) { const level = cachedClasses_1.game.GetLevel(); const thisStage = level.GetStage(); return stages.includes(thisStage); } /** Helper function to check if the current stage is equal to or higher than the given stage. */ function onStageOrHigher(stage) { const level = cachedClasses_1.game.GetLevel(); const thisStage = level.GetStage(); return thisStage >= stage; } /** Helper function to check if the current stage is equal to or higher than the given stage. */ function onStageOrLower(stage) { const level = cachedClasses_1.game.GetLevel(); const thisStage = level.GetStage(); return thisStage <= stage; } /** * Helper function to check if the current stage matches one of the given stage types. * * This function is variadic, which means you can pass as many room types as you want to match for. */ function onStageType(...stageTypes) { const level = cachedClasses_1.game.GetLevel(); const thisStageType = level.GetStageType(); return stageTypes.includes(thisStageType); } /** * Helper function to check if the current stage is one that has the possibility to grant a natural * Devil Room or Angel Room after killing the boss. */ function onStageWithNaturalDevilRoom() { const effectiveStage = getEffectiveStage(); return isStageWithNaturalDevilRoom(effectiveStage); } /** * Helper function to check if the current stage is one that will have a random collectible drop * upon defeating the boss of the floor. * * This happens on most stages but will not happen on Depths 2, Womb 2, Sheol, Cathedral, Dark Room, * The Chest, and Home (due to the presence of a story boss). * * Note that even though Delirium does not drop a random boss collectible, The Void is still * considered to be a stage that has a random boss collectible since all of the non-Delirium Boss * Rooms will drop random boss collectibles. */ function onStageWithRandomBossCollectible() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); return isStageWithRandomBossCollectible(stage); } /** * Helper function to check if the current stage will spawn a locked door to Downpour/Dross after * defeating the boss. */ function onStageWithSecretExitToDownpour() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); return isStageWithSecretExitToDownpour(stage); } /** * Helper function to check if the current stage will spawn a spiked door to Mausoleum/Gehenna after * defeating the boss. */ function onStageWithSecretExitToMausoleum() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return isStageWithSecretExitToMausoleum(stage, stageType); } /** * Helper function to check if the current stage will spawn a wooden door to Mines/Ashpit after * defeating the boss. */ function onStageWithSecretExitToMines() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return isStageWithSecretExitToMines(stage, stageType); } /** * Helper function to check if the current stage is one that would create a trapdoor if We Need to * Go Deeper was used. */ function onStageWithShovelTrapdoors() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); return isStageWithShovelTrapdoors(stage, stageType); } /** * Helper function to check if the current stage is one with a story boss. Specifically, this is * Depths 2 (Mom), Womb 2 (Mom's Heart / It Lives), Blue Womb (Hush), Sheol (Satan), Cathedral * (Isaac), Dark Room (Lamb), The Chest (Blue Baby), The Void (Delirium), and Home (Dogma / The * Beast). */ function onStageWithStoryBoss() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); return isStageWithStoryBoss(stage); } /** * Helper function to directly warp to a specific stage using the "stage" console command. * * This is different from the vanilla `Level.SetStage` method, which will change the stage and/or * stage type of the current floor without moving the player to a new floor. * * Note that if you use this function on game frame 0, it will confuse the * `POST_GAME_STARTED_REORDERED`, `POST_NEW_LEVEL_REORDERED`, and `POST_NEW_ROOM_REORDERED` custom * callbacks. If you are using the function in this situation, remember to call the * `reorderedCallbacksSetStage` function. * * @param stage The stage number to warp to. * @param stageType The stage type to warp to. * @param reseed Optional. Whether to reseed the floor upon arrival. Default is false. Set this to * true if you are warping to the same stage but a different stage type (or else the * floor layout will be identical to the old floor). */ function setStage(stage, stageType, reseed = false) { // Build the command that will take us to the next floor. const stageTypeSuffix = getStageTypeSuffix(stageType); const command = `stage ${stage}${stageTypeSuffix}`; (0, log_1.log)(`Warping to a stage with a console command of: ${command}`); Isaac.ExecuteCommand(command); if (reseed) { // Doing a "reseed" immediately after a "stage" command won't mess anything up. (0, log_1.log)("Reseeding the floor with a console command of: reseed"); Isaac.ExecuteCommand("reseed"); } }