"use strict";
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);
