UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

227 lines (226 loc) • 11.5 kB
"use strict"; // This emulates the vanilla versus screen that shows up when you enter a boss room. Object.defineProperty(exports, "__esModule", { value: true }); exports.playVersusScreenAnimation = playVersusScreenAnimation; exports.versusScreenPostRender = versusScreenPostRender; const isaac_typescript_definitions_1 = require("isaac-typescript-definitions"); const cachedClasses_1 = require("../../../../core/cachedClasses"); const array_1 = require("../../../../functions/array"); const bosses_1 = require("../../../../functions/bosses"); const roomData_1 = require("../../../../functions/roomData"); const string_1 = require("../../../../functions/string"); const ui_1 = require("../../../../functions/ui"); const utils_1 = require("../../../../functions/utils"); const versusScreen_1 = require("../../../../functions/versusScreen"); const versusScreenBackgroundColors_1 = require("../../../../objects/versusScreenBackgroundColors"); const versusScreenDirtSpotColors_1 = require("../../../../objects/versusScreenDirtSpotColors"); const constants_1 = require("./constants"); const v_1 = require("./v"); const DEFAULT_STAGE_ID = isaac_typescript_definitions_1.StageID.BASEMENT; const VERSUS_SCREEN_ANIMATION_NAME = "Scene"; /** The layers range from 0 to 13. */ const NUM_VERSUS_SCREEN_ANM2_LAYERS = 14; /** Corresponds to "resources/gfx/ui/boss/versusscreen.anm2". */ var VersusScreenLayer; (function (VersusScreenLayer) { VersusScreenLayer[VersusScreenLayer["BACKGROUND"] = 0] = "BACKGROUND"; VersusScreenLayer[VersusScreenLayer["FRAME"] = 1] = "FRAME"; /** The boss dirt spot. */ VersusScreenLayer[VersusScreenLayer["BOSS_SPOT"] = 2] = "BOSS_SPOT"; /** The player dirt spot. */ VersusScreenLayer[VersusScreenLayer["PLAYER_SPOT"] = 3] = "PLAYER_SPOT"; VersusScreenLayer[VersusScreenLayer["BOSS_PORTRAIT"] = 4] = "BOSS_PORTRAIT"; VersusScreenLayer[VersusScreenLayer["PLAYER_PORTRAIT"] = 5] = "PLAYER_PORTRAIT"; VersusScreenLayer[VersusScreenLayer["PLAYER_NAME"] = 6] = "PLAYER_NAME"; VersusScreenLayer[VersusScreenLayer["BOSS_NAME"] = 7] = "BOSS_NAME"; VersusScreenLayer[VersusScreenLayer["VS_TEXT"] = 8] = "VS_TEXT"; VersusScreenLayer[VersusScreenLayer["BOSS_DOUBLE"] = 9] = "BOSS_DOUBLE"; VersusScreenLayer[VersusScreenLayer["DT_TEXT"] = 10] = "DT_TEXT"; VersusScreenLayer[VersusScreenLayer["OVERLAY"] = 11] = "OVERLAY"; /** * We only need to render either the normal player portrait layer or the alternate player portrait * layer. Rendering both will cause the player not to shake. */ VersusScreenLayer[VersusScreenLayer["PLAYER_PORTRAIT_ALT"] = 12] = "PLAYER_PORTRAIT_ALT"; VersusScreenLayer[VersusScreenLayer["BOSS_PORTRAIT_GROUND"] = 13] = "BOSS_PORTRAIT_GROUND"; VersusScreenLayer[VersusScreenLayer["BOSS_PORTRAIT_2_GROUND"] = 14] = "BOSS_PORTRAIT_2_GROUND"; })(VersusScreenLayer || (VersusScreenLayer = {})); /** These are the non-special layers that we will render last. */ const OTHER_ANM2_LAYERS = (0, array_1.arrayRemove)((0, utils_1.eRange)(NUM_VERSUS_SCREEN_ANM2_LAYERS), VersusScreenLayer.BACKGROUND, VersusScreenLayer.BOSS_SPOT, VersusScreenLayer.PLAYER_SPOT, VersusScreenLayer.OVERLAY, VersusScreenLayer.PLAYER_PORTRAIT_ALT); const VANILLA_VERSUS_PLAYBACK_SPEED = 0.5; /** We lazy load the sprite when first needed. */ const versusScreenSprite = Sprite(); /** * We lazy load the sprite when first needed. * * Unfortunately, we must split the background layer into an entirely different sprite so that we * can color it with the `Color` field. */ const versusScreenBackgroundSprite = Sprite(); /** * We lazy load the sprite when first needed. * * Unfortunately, we must split the dirt layer into an entirely different sprite so that we can * color it with the `Color` field. */ const versusScreenDirtSpotSprite = Sprite(); function playVersusScreenAnimation(customStage, disableAllSound, pause, runInNFrames) { const room = cachedClasses_1.game.GetRoom(); const roomType = room.GetType(); const roomCleared = room.IsClear(); const hud = cachedClasses_1.game.GetHUD(); if (roomType !== isaac_typescript_definitions_1.RoomType.BOSS) { return; } if (roomCleared) { return; } if (willVanillaVersusScreenPlay()) { // Since we are on an invalid stage, the versus screen will have a completely black background. // Revert to using the background from the default stage. const level = cachedClasses_1.game.GetLevel(); level.SetStage(constants_1.DEFAULT_BASE_STAGE, constants_1.DEFAULT_BASE_STAGE_TYPE); runInNFrames.runNextGameFrame(() => { const futureLevel = cachedClasses_1.game.GetLevel(); futureLevel.SetStage(constants_1.CUSTOM_FLOOR_STAGE, constants_1.CUSTOM_FLOOR_STAGE_TYPE); }); return; } v_1.v.run.showingBossVersusScreen = true; pause.pause(); hud.SetVisible(false); disableAllSound.disableAllSound(constants_1.CUSTOM_STAGE_FEATURE_NAME); // In vanilla, the "overlay.png" file has a white background. We must convert it to a PNG that // uses a transparent background in order for the background behind it to be visible. We use the // same "overlay.png" file as StageAPI uses for this purpose. if (!versusScreenSprite.IsLoaded()) { versusScreenSprite.Load("gfx/ui/boss/versusscreen.anm2", false); versusScreenSprite.ReplaceSpritesheet(VersusScreenLayer.OVERLAY, `${constants_1.ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/overlay.png`); } // Player { const { namePNGPath, portraitPNGPath } = getPlayerPNGPaths(); versusScreenSprite.ReplaceSpritesheet(VersusScreenLayer.PLAYER_NAME, namePNGPath); versusScreenSprite.ReplaceSpritesheet(VersusScreenLayer.PLAYER_PORTRAIT, portraitPNGPath); } // Boss { const { namePNGPath, portraitPNGPath } = getBossPNGPaths(customStage); const trimmedNamePNGPath = (0, string_1.removeCharactersBefore)(namePNGPath, "gfx/"); versusScreenSprite.ReplaceSpritesheet(VersusScreenLayer.BOSS_NAME, trimmedNamePNGPath); const trimmedPortraitPNGPath = (0, string_1.removeCharactersBefore)(portraitPNGPath, "gfx/"); versusScreenSprite.ReplaceSpritesheet(VersusScreenLayer.BOSS_PORTRAIT, trimmedPortraitPNGPath); } versusScreenSprite.LoadGraphics(); if (!versusScreenBackgroundSprite.IsLoaded()) { versusScreenBackgroundSprite.Load("gfx/ui/boss/versusscreen.anm2", true); } let backgroundColor = versusScreenBackgroundColors_1.VERSUS_SCREEN_BACKGROUND_COLORS[DEFAULT_STAGE_ID]; if (customStage.versusScreen?.backgroundColor !== undefined) { const { r, g, b, a } = customStage.versusScreen.backgroundColor; backgroundColor = Color(r, g, b, a); } versusScreenBackgroundSprite.Color = backgroundColor; if (!versusScreenDirtSpotSprite.IsLoaded()) { versusScreenDirtSpotSprite.Load("gfx/ui/boss/versusscreen.anm2", true); } let dirtSpotColor = versusScreenDirtSpotColors_1.VERSUS_SCREEN_DIRT_SPOT_COLORS[DEFAULT_STAGE_ID]; if (customStage.versusScreen?.dirtSpotColor !== undefined) { const { r, g, b } = customStage.versusScreen.dirtSpotColor; dirtSpotColor = Color(r, g, b); } versusScreenDirtSpotSprite.Color = dirtSpotColor; for (const sprite of [ versusScreenBackgroundSprite, versusScreenDirtSpotSprite, versusScreenSprite, ]) { sprite.Play(VERSUS_SCREEN_ANIMATION_NAME, true); sprite.PlaybackSpeed = VANILLA_VERSUS_PLAYBACK_SPEED; } } function willVanillaVersusScreenPlay() { const bosses = (0, bosses_1.getBosses)(); return bosses.some((boss) => boss.GetBossID() !== 0); } /** Use the character of the 0th player. */ function getPlayerPNGPaths() { const player = Isaac.GetPlayer(); const character = player.GetPlayerType(); if (character === isaac_typescript_definitions_1.PlayerType.POSSESSOR) { error("Failed to get the player PNG paths since they are a possessor."); } const namePNGPath = (0, versusScreen_1.getCharacterNamePNGFilePath)(character); const portraitPNGPath = (0, versusScreen_1.getCharacterPortraitPNGFilePath)(character); return { namePNGPath, portraitPNGPath }; } /** Use the boss of the first boss found. */ function getBossPNGPaths(customStage) { // Prefer the PNG paths specified by the end-user, if any. const paths = getBossPNGPathsCustom(customStage); if (paths !== undefined) { return paths; } // If this is not a vanilla boss, default to showing question marks. const bosses = (0, bosses_1.getBosses)(); const firstBoss = bosses[0]; const bossID = firstBoss === undefined ? 0 : firstBoss.GetBossID(); if (bossID === 0) { const questionMarkPath = (0, versusScreen_1.getBossNamePNGFilePath)(isaac_typescript_definitions_1.BossID.BLUE_BABY); const namePNGPath = questionMarkPath; const portraitPNGPath = questionMarkPath; return { namePNGPath, portraitPNGPath }; } // If this is a vanilla boss, it will have a boss ID, and we can use the corresponding vanilla // files. const namePNGPath = (0, versusScreen_1.getBossNamePNGFilePath)(bossID); const portraitPNGPath = (0, versusScreen_1.getBossPortraitPNGFilePath)(bossID); return { namePNGPath, portraitPNGPath }; } function getBossPNGPathsCustom(customStage) { if (customStage.bossPool === undefined) { return undefined; } const roomSubType = (0, roomData_1.getRoomSubType)(); const matchingBossEntry = customStage.bossPool.find((bossEntry) => bossEntry.subType === roomSubType); if (matchingBossEntry === undefined) { return undefined; } return matchingBossEntry.versusScreen; } function finishVersusScreenAnimation(pause, disableAllSound) { const hud = cachedClasses_1.game.GetHUD(); v_1.v.run.showingBossVersusScreen = false; pause.unpause(); hud.SetVisible(true); disableAllSound.enableAllSound(constants_1.CUSTOM_STAGE_FEATURE_NAME); // The sound effect only plays once the versus cutscene is over. cachedClasses_1.sfxManager.Play(isaac_typescript_definitions_1.SoundEffect.CASTLE_PORTCULLIS); } // ModCallback.POST_RENDER (2) function versusScreenPostRender(pause, disableAllSound) { if (!v_1.v.run.showingBossVersusScreen) { return; } // We do not want to early return when the game is paused because we need to start displaying the // black screen as soon as the slide animation starts. if (versusScreenSprite.IsFinished(VERSUS_SCREEN_ANIMATION_NAME)) { finishVersusScreenAnimation(pause, disableAllSound); return; } const position = (0, ui_1.getScreenCenterPos)(); // First, we render the background. versusScreenBackgroundSprite.RenderLayer(VersusScreenLayer.BACKGROUND, position); versusScreenBackgroundSprite.Update(); // Second, we render the overlay. versusScreenSprite.RenderLayer(VersusScreenLayer.OVERLAY, position); // Third, we render the dirt. versusScreenDirtSpotSprite.RenderLayer(VersusScreenLayer.BOSS_SPOT, position); versusScreenDirtSpotSprite.RenderLayer(VersusScreenLayer.PLAYER_SPOT, position); versusScreenDirtSpotSprite.Update(); // Lastly, we render everything else. for (const layerID of OTHER_ANM2_LAYERS) { versusScreenSprite.RenderLayer(layerID, position); } versusScreenSprite.Update(); }