UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

195 lines (194 loc) • 9.95 kB
"use strict"; // This file handles drawing the walls and floors for custom stages. Object.defineProperty(exports, "__esModule", { value: true }); exports.setCustomStageBackdrop = setCustomStageBackdrop; const isaac_typescript_definitions_1 = require("isaac-typescript-definitions"); const cachedClasses_1 = require("../../../../core/cachedClasses"); const constants_1 = require("../../../../core/constants"); const LadderSubTypeCustom_1 = require("../../../../enums/LadderSubTypeCustom"); const array_1 = require("../../../../functions/array"); const entitiesSpecific_1 = require("../../../../functions/entitiesSpecific"); const rng_1 = require("../../../../functions/rng"); const roomShape_1 = require("../../../../functions/roomShape"); const string_1 = require("../../../../functions/string"); const utils_1 = require("../../../../functions/utils"); const ReadonlySet_1 = require("../../../../types/ReadonlySet"); const constants_2 = require("./constants"); var BackdropKind; (function (BackdropKind) { /** The "N" stands for narrow rooms. */ BackdropKind["N_FLOOR"] = "nFloors"; /** The "L" stands for L rooms. */ BackdropKind["L_FLOOR"] = "lFloors"; BackdropKind["WALL"] = "walls"; BackdropKind["CORNER"] = "corners"; })(BackdropKind || (BackdropKind = {})); /** This is created by the vanilla Basement files. */ const DEFAULT_BACKDROP = { nFloors: [`${constants_2.ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/backdrop/nfloor.png`], lFloors: [`${constants_2.ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/backdrop/lfloor.png`], // cspell:ignore lfloor walls: [`${constants_2.ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/backdrop/wall.png`], corners: [`${constants_2.ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/backdrop/corner.png`], }; const ROOM_SHAPE_WALL_ANM2_LAYERS = { [isaac_typescript_definitions_1.RoomShape.SHAPE_1x1]: 44, // 1 [isaac_typescript_definitions_1.RoomShape.IH]: 36, // 2 [isaac_typescript_definitions_1.RoomShape.IV]: 28, // 3 [isaac_typescript_definitions_1.RoomShape.SHAPE_1x2]: 58, // 4 [isaac_typescript_definitions_1.RoomShape.IIV]: 42, // 5 [isaac_typescript_definitions_1.RoomShape.SHAPE_2x1]: 63, // 6 [isaac_typescript_definitions_1.RoomShape.IIH]: 62, // 7 [isaac_typescript_definitions_1.RoomShape.SHAPE_2x2]: 63, // 8 [isaac_typescript_definitions_1.RoomShape.LTL]: 63, // 9 [isaac_typescript_definitions_1.RoomShape.LTR]: 63, // 10 [isaac_typescript_definitions_1.RoomShape.LBL]: 63, // 11 [isaac_typescript_definitions_1.RoomShape.LBR]: 63, // 12 }; // We don't use `as const` since we need the object to be indexable by all `RoomShape`. // eslint-disable-next-line complete/require-capital-const-assertions const ROOM_SHAPE_WALL_EXTRA_ANM2_LAYERS = { [isaac_typescript_definitions_1.RoomShape.SHAPE_2x1]: 7, // 6 [isaac_typescript_definitions_1.RoomShape.SHAPE_2x2]: 21, // 8 [isaac_typescript_definitions_1.RoomShape.LTL]: 19, // 9 [isaac_typescript_definitions_1.RoomShape.LTR]: 19, // 10 [isaac_typescript_definitions_1.RoomShape.LBL]: 19, // 11 [isaac_typescript_definitions_1.RoomShape.LBR]: 19, // 12 }; const WALL_OFFSET = Vector(-80, -80); /** Corresponds to "floor-backdrop.anm2". */ const L_FLOOR_ANM2_LAYERS = [16, 17]; /** Corresponds to "floor-backdrop.anm2". */ const N_FLOOR_ANM2_LAYERS = [18, 19]; /** * Normally, we would make a custom entity to represent a backdrop effect, but we don't want to * interfere with the "entities2.xml" file in end-user mods. Thus, we must select a vanilla effect * to masquerade as a backdrop effect. * * We arbitrarily choose a ladder for this purpose because it will not automatically despawn after * time passes, like most other effects. */ const BACKDROP_EFFECT_VARIANT = isaac_typescript_definitions_1.EffectVariant.LADDER; const BACKDROP_EFFECT_SUB_TYPE = LadderSubTypeCustom_1.LadderSubTypeCustom.CUSTOM_BACKDROP; const BACKDROP_ROOM_TYPE_SET = new ReadonlySet_1.ReadonlySet([ isaac_typescript_definitions_1.RoomType.DEFAULT, isaac_typescript_definitions_1.RoomType.BOSS, isaac_typescript_definitions_1.RoomType.MINI_BOSS, ]); function setCustomStageBackdrop(customStage) { const room = cachedClasses_1.game.GetRoom(); const roomType = room.GetType(); const decorationSeed = room.GetDecorationSeed(); const rng = (0, rng_1.newRNG)(decorationSeed); // We do not want to set the backdrop inside shops, Curse Rooms, and so on. if (!BACKDROP_ROOM_TYPE_SET.has(roomType)) { return; } spawnWallEntity(customStage, rng, false); spawnSecondWallEntity(customStage, rng); spawnFloorEntity(customStage, rng); } function getBackdropPNGPath(customStage, backdropKind, rng) { const backdrop = customStage.backdropPNGPaths ?? DEFAULT_BACKDROP; const pathArray = backdrop[backdropKind]; const randomPath = (0, array_1.getRandomArrayElement)(pathArray, rng); return (0, string_1.removeCharactersBefore)(randomPath, "gfx/"); } function spawnWallEntity(customStage, rng, isExtraWall) { const room = cachedClasses_1.game.GetRoom(); const roomShape = room.GetRoomShape(); // We spawn an effect instead of simply rendering a static sprite in order to emulate how vanilla // does this. (`EntityFlag.RENDER_WALL` is intended for this purpose.) const seed = 1; const wallEffect = (0, entitiesSpecific_1.spawnEffectWithSeed)(BACKDROP_EFFECT_VARIANT, BACKDROP_EFFECT_SUB_TYPE, constants_1.VectorZero, seed); wallEffect.AddEntityFlags(isaac_typescript_definitions_1.EntityFlag.RENDER_WALL); const sprite = wallEffect.GetSprite(); sprite.Load(`${constants_2.ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/wall-backdrop.anm2`, false); const wallLayersArray = isExtraWall ? ROOM_SHAPE_WALL_EXTRA_ANM2_LAYERS : ROOM_SHAPE_WALL_ANM2_LAYERS; const numWallLayers = wallLayersArray[roomShape]; (0, utils_1.assertDefined)(numWallLayers, `Failed to get the layers when creating the backdrop for custom stage: ${customStage.name}`); if ((0, roomShape_1.isLRoomShape)(roomShape)) { const cornerPNGPath = getBackdropPNGPath(customStage, BackdropKind.CORNER, rng); sprite.ReplaceSpritesheet(0, cornerPNGPath); } for (const layerID of (0, utils_1.iRange)(1, numWallLayers)) { const wallPNGPath = getBackdropPNGPath(customStage, BackdropKind.WALL, rng); sprite.ReplaceSpritesheet(layerID, wallPNGPath); } const topLeftPos = room.GetTopLeftPos(); const renderPos = topLeftPos.add(WALL_OFFSET); const modifiedOffset = renderPos.div(40).mul(26); wallEffect.SpriteOffset = modifiedOffset; sprite.LoadGraphics(); const roomShapeName = isaac_typescript_definitions_1.RoomShape[roomShape]; const animation = (0, string_1.trimPrefix)(roomShapeName, "SHAPE_"); const modifiedAnimation = isExtraWall ? `${animation}X` : animation; sprite.Play(modifiedAnimation, true); } function spawnSecondWallEntity(customStage, rng) { const room = cachedClasses_1.game.GetRoom(); const roomShape = room.GetRoomShape(); const extraLayers = ROOM_SHAPE_WALL_EXTRA_ANM2_LAYERS[roomShape]; const roomShapeHasExtraLayers = extraLayers !== undefined; if (roomShapeHasExtraLayers) { spawnWallEntity(customStage, rng, true); } } function spawnFloorEntity(customStage, rng) { const room = cachedClasses_1.game.GetRoom(); const roomShape = room.GetRoomShape(); // We spawn an effect instead of simply rendering a static sprite in order to emulate how vanilla // does this. (`EntityFlag.RENDER_FLOOR` is intended for this purpose.) const seed = 1; const floorEffect = (0, entitiesSpecific_1.spawnEffectWithSeed)(BACKDROP_EFFECT_VARIANT, 0, constants_1.VectorZero, seed); floorEffect.AddEntityFlags(isaac_typescript_definitions_1.EntityFlag.RENDER_FLOOR); const sprite = floorEffect.GetSprite(); sprite.Load(`${constants_2.ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/floor-backdrop.anm2`, false); const numFloorLayers = getNumFloorLayers(roomShape); if (numFloorLayers !== undefined) { for (const layerID of (0, utils_1.eRange)(numFloorLayers)) { // The wall spritesheet is used for the "normal" floors. const wallPNGPath = getBackdropPNGPath(customStage, BackdropKind.WALL, rng); sprite.ReplaceSpritesheet(layerID, wallPNGPath); } } else if ((0, roomShape_1.isLRoomShape)(roomShape)) { for (const layerID of L_FLOOR_ANM2_LAYERS) { const LFloorPNGPath = getBackdropPNGPath(customStage, BackdropKind.L_FLOOR, rng); sprite.ReplaceSpritesheet(layerID, LFloorPNGPath); } } else if ((0, roomShape_1.isNarrowRoom)(roomShape)) { for (const layerID of N_FLOOR_ANM2_LAYERS) { const NFloorPNGPath = getBackdropPNGPath(customStage, BackdropKind.N_FLOOR, rng); sprite.ReplaceSpritesheet(layerID, NFloorPNGPath); } } const topLeftPos = room.GetTopLeftPos(); const renderPos = topLeftPos; const modifiedOffset = renderPos.div(40).mul(26); // The magic numbers are copied from StageAPI. floorEffect.SpriteOffset = modifiedOffset; sprite.LoadGraphics(); const roomShapeName = isaac_typescript_definitions_1.RoomShape[roomShape]; const animation = (0, string_1.trimPrefix)(roomShapeName, "SHAPE_"); sprite.Play(animation, true); } function getNumFloorLayers(roomShape) { switch (roomShape) { case isaac_typescript_definitions_1.RoomShape.SHAPE_1x1: { return 4; } case isaac_typescript_definitions_1.RoomShape.SHAPE_1x2: case isaac_typescript_definitions_1.RoomShape.SHAPE_2x1: { return 8; } case isaac_typescript_definitions_1.RoomShape.SHAPE_2x2: { return 16; } default: { // We have explicit logic elsewhere to handle narrow rooms and L rooms. return undefined; } } }