UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

219 lines (218 loc) • 9.89 kB
"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.GameReorderedCallbacks = void 0; const isaac_typescript_definitions_1 = require("isaac-typescript-definitions"); const cachedClasses_1 = require("../../../core/cachedClasses"); const decorators_1 = require("../../../decorators"); const frames_1 = require("../../../functions/frames"); const Feature_1 = require("../../private/Feature"); /** * By default, callbacks fire in the following order: * - `POST_NEW_ROOM` --> `POST_NEW_LEVEL` --> `POST_GAME_STARTED` * * It is easier to write mod code if the callbacks run in a more logical order: * - `POST_GAME_STARTED` --> `POST_NEW_LEVEL` --> `POST_NEW_ROOM` * * `isaacscript-common` provides three new callbacks that change the order to this: * - `POST_GAME_STARTED_REORDERED` * - `POST_NEW_LEVEL_REORDERED` * - `POST_NEW_ROOM_REORDERED` * * Additionally, there are some helper functions listed below that can deal with some edge cases * that you may run into with these callbacks. */ class GameReorderedCallbacks extends Feature_1.Feature { /** Used to detect a player resuming a saved run. */ renderFrameRunStarted = null; currentStage = null; currentStageType = null; usedGlowingHourGlass = false; forceNewLevel = false; forceNewRoom = false; postGameStartedReordered; postNewLevelReordered; postNewRoomReordered; postGameStartedReorderedLast; /** @internal */ constructor(postGameStartedReordered, postNewLevelReordered, postNewRoomReordered, postGameStartedReorderedLast) { super(); this.callbacksUsed = [ // 3 [ isaac_typescript_definitions_1.ModCallback.POST_USE_ITEM, this.postUseItemGlowingHourGlass, [isaac_typescript_definitions_1.CollectibleType.GLOWING_HOUR_GLASS], ], // 9 [isaac_typescript_definitions_1.ModCallback.POST_PLAYER_INIT, this.postPlayerInit], // 15 // eslint-disable-next-line @typescript-eslint/no-deprecated [isaac_typescript_definitions_1.ModCallback.POST_GAME_STARTED, this.postGameStarted], // 17 [isaac_typescript_definitions_1.ModCallback.PRE_GAME_EXIT, this.preGameExit], // 18 // eslint-disable-next-line @typescript-eslint/no-deprecated [isaac_typescript_definitions_1.ModCallback.POST_NEW_LEVEL, this.postNewLevel], // 19 // eslint-disable-next-line @typescript-eslint/no-deprecated [isaac_typescript_definitions_1.ModCallback.POST_NEW_ROOM, this.postNewRoom], ]; this.postGameStartedReordered = postGameStartedReordered; this.postNewLevelReordered = postNewLevelReordered; this.postNewRoomReordered = postNewRoomReordered; this.postGameStartedReorderedLast = postGameStartedReorderedLast; } // ModCallback.POST_USE_ITEM (3) // CollectibleType.GLOWING_HOUR_GLASS (422) postUseItemGlowingHourGlass = () => { // If Glowing Hourglass is used on the first room of a floor, it will send the player to the // previous floor without triggering the `POST_NEW_LEVEL` callback. Manually check for this. this.usedGlowingHourGlass = true; return undefined; }; // ModCallback.POST_PLAYER_INIT (9) postPlayerInit = (_player) => { this.renderFrameRunStarted ??= Isaac.GetFrameCount(); }; // ModCallback.POST_GAME_STARTED (15) postGameStarted = (isContinued) => { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); const room = cachedClasses_1.game.GetRoom(); const roomType = room.GetType(); this.recordCurrentStage(); this.postGameStartedReordered.fire(isContinued); this.postGameStartedReorderedLast.fire(isContinued); if (!isContinued) { // The vanilla `POST_NEW_LEVEL` callback only fires on non-continued runs, which makes sense, // because we do not want to blow away level variables in this case. this.postNewLevelReordered.fire(stage, stageType); } this.postNewRoomReordered.fire(roomType); }; // ModCallback.PRE_GAME_EXIT (17) preGameExit = () => { this.renderFrameRunStarted = null; }; // ModCallback.POST_NEW_LEVEL (18) postNewLevel = () => { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); const room = cachedClasses_1.game.GetRoom(); const roomType = room.GetType(); if ((0, frames_1.onGameFrame)(0) && !this.forceNewLevel) { // Wait for the `POST_GAME_STARTED` callback to fire. return; } this.forceNewLevel = false; this.recordCurrentStage(); this.postNewLevelReordered.fire(stage, stageType); this.postNewRoomReordered.fire(roomType); }; // ModCallback.POST_NEW_ROOM (19) postNewRoom = () => { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); const room = cachedClasses_1.game.GetRoom(); const roomType = room.GetType(); if (this.usedGlowingHourGlass) { this.usedGlowingHourGlass = false; if (this.currentStage !== stage || this.currentStageType !== stageType) { // The player has used the Glowing Hourglass to take them to the previous floor (which does // not trigger the `POST_NEW_LEVEL` callback). Emulate what happens in the `POST_NEW_LEVEL` // callback. this.recordCurrentStage(); this.postNewLevelReordered.fire(stage, stageType); this.postNewRoomReordered.fire(roomType); return; } } if (((0, frames_1.onGameFrame)(0) || (0, frames_1.onRenderFrame)(this.renderFrameRunStarted) || this.currentStage !== stage || this.currentStageType !== stageType) && !this.forceNewRoom) { return; } this.forceNewRoom = false; this.postNewRoomReordered.fire(roomType); }; recordCurrentStage() { const level = cachedClasses_1.game.GetLevel(); const stage = level.GetStage(); const stageType = level.GetStageType(); this.currentStage = stage; this.currentStageType = stageType; } /** * Helper function to tell the `POST_NEW_LEVEL_REORDERED` callback that it should always fire on * the next `POST_NEW_LEVEL`. * * If some specific cases, mods can change the current level during run initialization on the 0th * frame. (For example, if you had a mod that made the player start the run in Caves instead of * Basement.) However, due to how the callback reordering works, the `POST_NEW_LEVEL_REORDERED` * callback will never fire on the 0th frame. To get around this, call this function before * changing levels to temporarily force the callback to fire. * * In order to use this function, you must upgrade your mod with * `ISCFeature.GAME_REORDERED_CALLBACKS`. * * @public */ forceNewLevelCallback() { this.forceNewLevel = true; } /** * Helper function to tell the `POST_NEW_ROOM_REORDERED` callback that it should always fire on * the next `POST_NEW_ROOM`. * * If some specific cases, mods can change the current room during run initialization on the 0th * frame. (For example, if you had a mod that made the player start the Treasure Room of Basement * 1 instead of the normal starting room.) However, due to how the callback reordering works, the * `POST_NEW_ROOM_REORDERED` callback will never fire on the 0th frame. To get around this, call * this function before changing rooms to temporarily force the callback to fire. * * In order to use this function, you must upgrade your mod with * `ISCFeature.GAME_REORDERED_CALLBACKS`. * * @public */ forceNewRoomCallback() { this.forceNewRoom = true; } /** * Helper function to manually set the variables that the reordered callback logic uses to track * the current stage and stage type. * * This is useful because if the stage is changed with the `Game.SetStage` method (or the * `setStage` helper function), the reordered callbacks will stop working. * * In order to use this function, you must upgrade your mod with * `ISCFeature.GAME_REORDERED_CALLBACKS`. * * @public */ reorderedCallbacksSetStage(stage, stageType) { this.currentStage = stage; this.currentStageType = stageType; } } exports.GameReorderedCallbacks = GameReorderedCallbacks; __decorate([ decorators_1.Exported ], GameReorderedCallbacks.prototype, "forceNewLevelCallback", null); __decorate([ decorators_1.Exported ], GameReorderedCallbacks.prototype, "forceNewRoomCallback", null); __decorate([ decorators_1.Exported ], GameReorderedCallbacks.prototype, "reorderedCallbacksSetStage", null);