isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
160 lines (159 loc) • 8.12 kB
JavaScript
;
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.PreventGridEntityRespawn = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedClasses_1 = require("../../../core/cachedClasses");
const decorators_1 = require("../../../decorators");
const ISCFeature_1 = require("../../../enums/ISCFeature");
const ModCallbackCustom_1 = require("../../../enums/ModCallbackCustom");
const array_1 = require("../../../functions/array");
const gridEntities_1 = require("../../../functions/gridEntities");
const players_1 = require("../../../functions/players");
const roomData_1 = require("../../../functions/roomData");
const DefaultMap_1 = require("../../DefaultMap");
const Feature_1 = require("../../private/Feature");
const v = {
level: {
roomListIndexToDecorationGridIndexes: new DefaultMap_1.DefaultMap(() => []),
},
room: {
manuallyUsingShovel: false,
},
};
class PreventGridEntityRespawn extends Feature_1.Feature {
/** @internal */
v = v;
runInNFrames;
/** @internal */
constructor(runInNFrames) {
super();
this.featuresUsed = [ISCFeature_1.ISCFeature.RUN_IN_N_FRAMES];
this.callbacksUsed = [
// 23
[
isaac_typescript_definitions_1.ModCallback.PRE_USE_ITEM,
this.preUseItemWeNeedToGoDeeper,
[isaac_typescript_definitions_1.CollectibleType.WE_NEED_TO_GO_DEEPER],
],
];
this.customCallbacksUsed = [
[ModCallbackCustom_1.ModCallbackCustom.POST_NEW_ROOM_REORDERED, this.postNewRoomReordered],
];
this.runInNFrames = runInNFrames;
}
// ModCallback.PRE_USE_ITEM (23)
// CollectibleType.WE_NEED_TO_GO_DEEPER (84)
preUseItemWeNeedToGoDeeper = (_collectibleType, _rng, player, _useFlags, _activeSlot, _customVarData) => {
if (v.room.manuallyUsingShovel) {
return undefined;
}
const roomListIndex = (0, roomData_1.getRoomListIndex)();
if (!v.level.roomListIndexToDecorationGridIndexes.has(roomListIndex)) {
return undefined;
}
// Since the room was filled with decorations to prevent any grid entities from respawning, if
// the player uses a shovel, it will always reveal a crawl space. In order to restore the normal
// shovel functionality, we cancel the shovel use, remove all the decorations, wait a frame,
// manually use the shovel again, and then respawn the decorations. (We can't do it all on this
// frame because updating the room causes two invocations of the shovel to happen.)
const decorations = (0, gridEntities_1.getGridEntities)(isaac_typescript_definitions_1.GridEntityType.DECORATION);
for (const decoration of decorations) {
(0, gridEntities_1.removeGridEntity)(decoration, false);
}
const entityPtr = EntityPtr(player);
this.runInNFrames.runNextGameFrame(() => {
const futurePlayer = (0, players_1.getPlayerFromPtr)(entityPtr);
if (futurePlayer === undefined) {
return;
}
const futureRoomListIndex = (0, roomData_1.getRoomListIndex)();
if (futureRoomListIndex !== roomListIndex) {
return;
}
v.room.manuallyUsingShovel = true;
futurePlayer.UseActiveItem(isaac_typescript_definitions_1.CollectibleType.WE_NEED_TO_GO_DEEPER);
v.room.manuallyUsingShovel = false;
const decorationGridIndexes = v.level.roomListIndexToDecorationGridIndexes.getAndSetDefault(roomListIndex);
(0, array_1.emptyArray)(decorationGridIndexes);
this.preventGridEntityRespawn();
});
// Cancel the original effect.
return true;
};
// ModCallbackCustom.POST_NEW_ROOM_REORDERED
postNewRoomReordered = () => {
this.setDecorationsInvisible();
};
/**
* Every time we re-enter the room, the sprites for all of the decorations will come back, so we
* have to remove them again.
*/
setDecorationsInvisible() {
const room = cachedClasses_1.game.GetRoom();
const roomListIndex = (0, roomData_1.getRoomListIndex)();
const decorationGridIndexes = v.level.roomListIndexToDecorationGridIndexes.get(roomListIndex);
if (decorationGridIndexes === undefined) {
return;
}
for (const gridIndex of decorationGridIndexes) {
const gridEntity = room.GetGridEntity(gridIndex);
if (gridEntity !== undefined) {
// Other grid entities may have spawned, like trapdoors or crawl spaces. Thus, only make
// decorations invisible.
const gridEntityType = gridEntity.GetType();
if (gridEntityType === isaac_typescript_definitions_1.GridEntityType.DECORATION) {
(0, gridEntities_1.setGridEntityInvisible)(gridEntity);
}
}
}
}
/**
* Helper function to prevent any removed grid entities from respawning if the player re-enters
* the current room.
*
* This is accomplished by spawning a new grid entity on every tile that does not already have a
* grid entity. This will force the game to spawn the new grid entity instead of the old one. The
* natural grid entity to choose for this purpose is a decoration, since it is non-interacting.
* Then, the decorations are made invisible and any shovel uses are intercepted to avoid creating
* a crawl space (instead of a trapdoor).
*
* Another option besides decorations would be to use a pressure plates with a state of 1, which
* is a state that is normally unused by the game and makes it invisible & persistent. However,
* pickups will not be able to spawn on pressure plates, which lead to various bugs (e.g. pickups
* spawning on top of pits). Thus, using a decoration is preferable.
*
* Yet another option to accomplish this would be to replace the room data with that of an empty
* room. However, the room data must exactly match the room type, the room shape, and the doors,
* so this is not possible to do in a robust way without adding empty rooms to the mod's `content`
* folder to draw the data from.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.PREVENT_GRID_ENTITY_RESPAWN`.
*/
preventGridEntityRespawn() {
const room = cachedClasses_1.game.GetRoom();
const roomListIndex = (0, roomData_1.getRoomListIndex)();
const decorationGridIndexes = v.level.roomListIndexToDecorationGridIndexes.getAndSetDefault(roomListIndex);
for (const gridIndex of (0, gridEntities_1.getAllGridIndexes)()) {
const existingGridEntity = room.GetGridEntity(gridIndex);
if (existingGridEntity !== undefined) {
continue;
}
const decoration = (0, gridEntities_1.spawnGridEntity)(isaac_typescript_definitions_1.GridEntityType.DECORATION, gridIndex);
if (decoration !== undefined) {
(0, gridEntities_1.setGridEntityInvisible)(decoration);
}
decorationGridIndexes.push(gridIndex);
}
}
}
exports.PreventGridEntityRespawn = PreventGridEntityRespawn;
__decorate([
decorators_1.Exported
], PreventGridEntityRespawn.prototype, "preventGridEntityRespawn", null);