UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

324 lines (323 loc) • 14.4 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.RunInNFrames = 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 array_1 = require("../../../functions/array"); const run_1 = require("../../../functions/run"); const Feature_1 = require("../../private/Feature"); const v = { run: { queuedGameFunctions: [], queuedRenderFunctions: [], intervalGameFunctions: [], intervalRenderFunctions: [], }, }; class RunInNFrames extends Feature_1.Feature { /** @internal */ v = v; vConditionalFunc = () => false; roomHistory; /** @internal */ constructor(roomHistory) { super(); this.featuresUsed = [ISCFeature_1.ISCFeature.ROOM_HISTORY]; this.callbacksUsed = [ // 1 [isaac_typescript_definitions_1.ModCallback.POST_UPDATE, this.postUpdate], // 2 [isaac_typescript_definitions_1.ModCallback.POST_RENDER, this.postRender], ]; this.roomHistory = roomHistory; } // ModCallback.POST_UPDATE (1) postUpdate = () => { const gameFrameCount = cachedClasses_1.game.GetFrameCount(); const numRoomsEntered = this.roomHistory.getNumRoomsEntered(); checkExecuteQueuedFunctions(v.run.queuedGameFunctions, gameFrameCount, numRoomsEntered); checkExecuteIntervalFunctions(v.run.intervalGameFunctions, gameFrameCount, numRoomsEntered); }; // ModCallback.POST_RENDER (2) postRender = () => { const renderFrameCount = Isaac.GetFrameCount(); const numRoomsEntered = this.roomHistory.getNumRoomsEntered(); checkExecuteQueuedFunctions(v.run.queuedRenderFunctions, renderFrameCount, numRoomsEntered); checkExecuteIntervalFunctions(v.run.intervalRenderFunctions, renderFrameCount, numRoomsEntered); }; /** * Helper function to restart on the next render frame. Useful because it is impossible to restart * the game inside of the `POST_NEW_ROOM`, `POST_NEW_LEVEL`, or `POST_GAME_STARTED` callbacks when * a run is first starting. * * In order to use this function, you must upgrade your mod with `ISCFeature.RUN_IN_N_FRAMES`. * * @param character Optional. If specified, will restart the game as the specified character. * @public */ restartNextRenderFrame(character) { this.runNextRenderFrame(() => { (0, run_1.restart)(character); }); } /** * Supply a function to run N game frames from now in the `POST_UPDATE` callback. * * For a usage example, see the documentation for the `runNextGameFrame`, which is used in a * similar way. * * Note that this function will not handle saving and quitting. If a player saving and quitting * before the deferred function fires would cause a bug in your mod, then you should handle * deferred functions manually using serializable data. * * In order to use this function, you must upgrade your mod with `ISCFeature.RUN_IN_N_FRAMES`. * * @param func The function to run. * @param numGameFrames The amount of game frames to wait before running the function. * @param cancelIfRoomChanges Optional. Whether to cancel running the function if a new room is * loaded in the interim. Default is false. * @public */ runInNGameFrames(func, numGameFrames, cancelIfRoomChanges = false) { const gameFrameCount = cachedClasses_1.game.GetFrameCount(); const numRoomsEntered = this.roomHistory.getNumRoomsEntered(); const frameCountToFire = gameFrameCount + numGameFrames; const queuedFunction = { func, frameCountToFire, numRoomsEntered, cancelIfRoomChanges, }; v.run.queuedGameFunctions.push(queuedFunction); } /** * Supply a function to run N render frames from now in the `POST_RENDER` callback. * * For a usage example, see the documentation for the `runNextGameFrame`, which is used in a * similar way. * * Note that this function will not handle saving and quitting. If a player saving and quitting * before the deferred function fires would cause a bug in your mod, then you should handle * deferred functions manually using serializable data. * * In order to use this function, you must upgrade your mod with `ISCFeature.RUN_IN_N_FRAMES`. * * @param func The function to run. * @param numRenderFrames The amount of render frames to wait before running the function. * @param cancelIfRoomChanges Optional. Whether to cancel running the function if a new room is * loaded in the interim. Default is false. * @public */ runInNRenderFrames(func, numRenderFrames, cancelIfRoomChanges = false) { const renderFrameCount = Isaac.GetFrameCount(); const numRoomsEntered = this.roomHistory.getNumRoomsEntered(); const frameCountToFire = renderFrameCount + numRenderFrames; const queuedFunction = { func, frameCountToFire, numRoomsEntered, cancelIfRoomChanges, }; v.run.queuedRenderFunctions.push(queuedFunction); } /** * Supply a function to run on the next `POST_UPDATE` callback. * * For example: * * ```ts * const NUM_EXPLODER_EXPLOSIONS = 5; * * function useItemExploder(player: EntityPlayer) { * playSound("exploderBegin"); * explode(player, NUM_EXPLODER_EXPLOSIONS); * } * * function explode(player: EntityPlayer, numFramesLeft: int) { * Isaac.Explode(player, undefined, 1); * numFramesLeft -= 1; * if (numFramesLeft === 0) { * runNextFrame(() => { * explode(player, numFramesLeft); * }); * } * } * ``` * * Note that this function will not handle saving and quitting. If a player saving and quitting * before the deferred function fires would cause a bug in your mod, then you should handle * deferred functions manually using serializable data. * * In order to use this function, you must upgrade your mod with `ISCFeature.RUN_IN_N_FRAMES`. * * @param func The function to run. * @param cancelIfRoomChanges Optional. Whether to cancel running the function if a new room is * loaded in the interim. Default is false. * @public */ runNextGameFrame(func, cancelIfRoomChanges = false) { this.runInNGameFrames(func, 1, cancelIfRoomChanges); } /** * Supply a function to run on the next `POST_RENDER` callback. * * For a usage example, see the documentation for the `runNextGameFrame`, which is used in a * similar way. * * Note that this function will not handle saving and quitting. * * In order to use this function, you must upgrade your mod with `ISCFeature.RUN_IN_N_FRAMES`. * * @param func The function to run. * @param cancelIfRoomChanges Optional. Whether to cancel running the function if a new room is * loaded in the interim. Default is false. * @public */ runNextRenderFrame(func, cancelIfRoomChanges = false) { this.runInNRenderFrames(func, 1, cancelIfRoomChanges); } /** * Supply a function to be repeatedly run on an interval of N game frames in the `POST_UPDATE` * callback. The function will continue to be fired until `false` is returned from the function. * * This is similar to the `setInterval` vanilla JavaScript function, except there is no * corresponding `clearInterval` function. (Instead, the return value from the supplied function * is used to stop the interval.) * * Note that this function will not handle saving and quitting. You must manually restart any * intervals if the player saves and quits in the middle of a run. * * In order to use this function, you must upgrade your mod with `ISCFeature.RUN_IN_N_FRAMES`. * * @param func The function to repeatedly run on an interval. * @param numGameFrames The amount of game frames to wait between each run. * @param runImmediately Whether to execute the function right now before waiting for the * interval. * @param cancelIfRoomChanges Optional. Whether to cancel running the function if a new room is * loaded in the interim. Default is false. * @public */ setIntervalGameFrames(func, numGameFrames, runImmediately, cancelIfRoomChanges = false) { if (runImmediately) { const returnValue = func(); if (!returnValue) { return; } } const gameFrameCount = cachedClasses_1.game.GetFrameCount(); const numRoomsEntered = this.roomHistory.getNumRoomsEntered(); const intervalFunction = { func, frameCountToFire: gameFrameCount + numGameFrames, numRoomsEntered, cancelIfRoomChanges, numIntervalFrames: numGameFrames, }; v.run.intervalGameFunctions.push(intervalFunction); } /** * Supply a function to be repeatedly run on an interval of N render frames in the `POST_RENDER` * callback. The function will continue to be fired until `false` is returned from the function. * * This is similar to the `setInterval` vanilla JavaScript function, except there is no * corresponding `clearInterval` function. (Instead, the return value from the supplied function * is used to stop the interval.) * * Note that this function will not handle saving and quitting. You must manually restart any * intervals if the player saves and quits in the middle of a run. * * In order to use this function, you must upgrade your mod with `ISCFeature.RUN_IN_N_FRAMES`. * * @param func The function to repeatedly run on an interval. * @param numRenderFrames The amount of game frames to wait between each run. * @param runImmediately Whether to execute the function right now before waiting for the * interval. * @param cancelIfRoomChanges Optional. Whether to cancel running the function if a new room is * loaded in the interim. Default is false. * @public */ setIntervalRenderFrames(func, numRenderFrames, runImmediately, cancelIfRoomChanges = false) { if (runImmediately) { const returnValue = func(); if (!returnValue) { return; } } const renderFrameCount = Isaac.GetFrameCount(); const numRoomsEntered = this.roomHistory.getNumRoomsEntered(); const intervalFunction = { func, frameCountToFire: renderFrameCount + numRenderFrames, numRoomsEntered, cancelIfRoomChanges, numIntervalFrames: numRenderFrames, }; v.run.intervalRenderFunctions.push(intervalFunction); } } exports.RunInNFrames = RunInNFrames; __decorate([ decorators_1.Exported ], RunInNFrames.prototype, "restartNextRenderFrame", null); __decorate([ decorators_1.Exported ], RunInNFrames.prototype, "runInNGameFrames", null); __decorate([ decorators_1.Exported ], RunInNFrames.prototype, "runInNRenderFrames", null); __decorate([ decorators_1.Exported ], RunInNFrames.prototype, "runNextGameFrame", null); __decorate([ decorators_1.Exported ], RunInNFrames.prototype, "runNextRenderFrame", null); __decorate([ decorators_1.Exported ], RunInNFrames.prototype, "setIntervalGameFrames", null); __decorate([ decorators_1.Exported ], RunInNFrames.prototype, "setIntervalRenderFrames", null); function checkExecuteQueuedFunctions( // eslint-disable-next-line complete/prefer-readonly-parameter-types queuedFunctions, frameCount, newNumRoomsEntered) { const firingFunctions = queuedFunctions.filter(({ frameCountToFire }) => frameCount >= frameCountToFire); for (const firingFunction of firingFunctions) { const { func, cancelIfRoomChanges, numRoomsEntered } = firingFunction; if (!cancelIfRoomChanges || numRoomsEntered === newNumRoomsEntered) { func(); } (0, array_1.arrayRemoveInPlace)(queuedFunctions, firingFunction); } } function checkExecuteIntervalFunctions( // eslint-disable-next-line complete/prefer-readonly-parameter-types intervalFunctions, frameCount, newNumRoomsEntered) { const firingFunctions = intervalFunctions.filter(({ frameCountToFire }) => frameCount >= frameCountToFire); for (const firingFunction of firingFunctions) { const { func, cancelIfRoomChanges, numRoomsEntered, numIntervalFrames } = firingFunction; let returnValue = false; if (!cancelIfRoomChanges || numRoomsEntered === newNumRoomsEntered) { returnValue = func(); } (0, array_1.arrayRemoveInPlace)(intervalFunctions, firingFunction); // Queue the next interval (as long as the function did not return false). if (returnValue) { const newIntervalFunction = { func, frameCountToFire: frameCount + numIntervalFrames, numRoomsEntered, cancelIfRoomChanges, numIntervalFrames, }; intervalFunctions.push(newIntervalFunction); } } }