isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
195 lines (194 loc) • 9.95 kB
JavaScript
"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;
}
}
}