isaacscript-common 6.6.1-dev.1 → 6.6.1-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cachedClasses.d.ts +1 -0
- package/cachedClasses.js +59 -0
- package/callbacks/customRevive.js +153 -0
- package/callbacks/itemPickup.js +64 -0
- package/callbacks/postAmbush.js +54 -0
- package/callbacks/postBombExploded.js +20 -0
- package/callbacks/postBombInitLate.js +28 -0
- package/callbacks/postBoneSwing.js +49 -0
- package/callbacks/postCollectibleEmpty.js +32 -0
- package/callbacks/postCollectibleInitFirst.js +27 -0
- package/callbacks/postCursedTeleport.js +120 -0
- package/callbacks/postCustomDoorEnter.js +205 -0
- package/callbacks/postDiceRoomActivated.js +39 -0
- package/callbacks/postDoorRender.js +20 -0
- package/callbacks/postDoorUpdate.js +20 -0
- package/callbacks/postEffectInitLate.js +28 -0
- package/callbacks/postEffectStateChanged.js +31 -0
- package/callbacks/postEsauJr.js +70 -0
- package/callbacks/postFamiliarInitLate.js +28 -0
- package/callbacks/postFamiliarStateChanged.js +31 -0
- package/callbacks/postFlip.js +59 -0
- package/callbacks/postGreedModeWave.js +31 -0
- package/callbacks/postGridEntity.js +107 -0
- package/callbacks/postGridEntityCollision.js +49 -0
- package/callbacks/postGridEntityRender.js +20 -0
- package/callbacks/postHolyMantleRemoved.js +33 -0
- package/callbacks/postItemDischarged.js +94 -0
- package/callbacks/postKnifeInitLate.js +28 -0
- package/callbacks/postLaserInitLate.js +28 -0
- package/callbacks/postNPCInitLate.js +28 -0
- package/callbacks/postNPCStateChanged.js +31 -0
- package/callbacks/postNewRoomEarly.js +69 -0
- package/callbacks/postPickupCollect.js +37 -0
- package/callbacks/postPickupInitFirst.js +52 -0
- package/callbacks/postPickupInitLate.js +28 -0
- package/callbacks/postPickupStateChanged.js +31 -0
- package/callbacks/postPitRender.js +20 -0
- package/callbacks/postPitUpdate.js +20 -0
- package/callbacks/postPlayerChangeHealth.js +43 -0
- package/callbacks/postPlayerChangeType.js +31 -0
- package/callbacks/postPlayerCollectible.js +74 -0
- package/callbacks/postPlayerFatalDamage.js +74 -0
- package/callbacks/postPlayerInitLate.js +28 -0
- package/callbacks/postPlayerReordered.js +109 -0
- package/callbacks/postPoopRender.js +20 -0
- package/callbacks/postPoopUpdate.js +20 -0
- package/callbacks/postPressurePlateRender.js +20 -0
- package/callbacks/postPressurePlateUpdate.js +20 -0
- package/callbacks/postProjectileInitLate.js +28 -0
- package/callbacks/postPurchase.js +39 -0
- package/callbacks/postRockRender.js +20 -0
- package/callbacks/postRockUpdate.js +20 -0
- package/callbacks/postRoomClearChanged.js +41 -0
- package/callbacks/postSacrifice.js +38 -0
- package/callbacks/postSlotDestroyed.js +64 -0
- package/callbacks/postSlotInitUpdate.js +49 -0
- package/callbacks/postSlotRender.js +48 -0
- package/callbacks/postSpikesRender.js +20 -0
- package/callbacks/postSpikesUpdate.js +20 -0
- package/callbacks/postTNTRender.js +20 -0
- package/callbacks/postTNTUpdate.js +20 -0
- package/callbacks/postTearInitLate.js +28 -0
- package/callbacks/postTearInitVeryLate.js +32 -0
- package/callbacks/postTransformation.js +37 -0
- package/callbacks/postTrinketBreak.js +64 -0
- package/callbacks/preBerserkDeath.js +35 -0
- package/callbacks/preNewLevel.js +45 -0
- package/callbacks/reorderedCallbacks.js +136 -0
- package/callbacks/subscriptions/postAmbushFinished.js +20 -0
- package/callbacks/subscriptions/postAmbushStarted.js +20 -0
- package/callbacks/subscriptions/postBombInitLate.js +20 -0
- package/callbacks/subscriptions/postBoneExploded.js +20 -0
- package/callbacks/subscriptions/postBoneSwing.js +16 -0
- package/callbacks/subscriptions/postCollectibleEmpty.js +21 -0
- package/callbacks/subscriptions/postCollectibleInitFirst.js +21 -0
- package/callbacks/subscriptions/postCursedTeleport.js +16 -0
- package/callbacks/subscriptions/postCustomDoorEnter.js +21 -0
- package/callbacks/subscriptions/postCustomRevive.js +21 -0
- package/callbacks/subscriptions/postDiceRoomActivated.js +21 -0
- package/callbacks/subscriptions/postDoorRender.js +22 -0
- package/callbacks/subscriptions/postDoorUpdate.js +22 -0
- package/callbacks/subscriptions/postEffectInitLate.js +20 -0
- package/callbacks/subscriptions/postEffectStateChanged.js +20 -0
- package/callbacks/subscriptions/postEsauJr.js +16 -0
- package/callbacks/subscriptions/postFamiliarInitLate.js +20 -0
- package/callbacks/subscriptions/postFamiliarStateChanged.js +20 -0
- package/callbacks/subscriptions/postFirstEsauJr.js +16 -0
- package/callbacks/subscriptions/postFirstFlip.js +16 -0
- package/callbacks/subscriptions/postFlip.js +16 -0
- package/callbacks/subscriptions/postGameStartedReordered.js +16 -0
- package/callbacks/subscriptions/postGreedModeWave.js +16 -0
- package/callbacks/subscriptions/postGridEntityBroken.js +28 -0
- package/callbacks/subscriptions/postGridEntityCollision.js +28 -0
- package/callbacks/subscriptions/postGridEntityInit.js +28 -0
- package/callbacks/subscriptions/postGridEntityRemove.js +26 -0
- package/callbacks/subscriptions/postGridEntityRender.js +28 -0
- package/callbacks/subscriptions/postGridEntityStateChanged.js +28 -0
- package/callbacks/subscriptions/postGridEntityUpdate.js +28 -0
- package/callbacks/subscriptions/postHolyMantleRemoved.js +25 -0
- package/callbacks/subscriptions/postItemDischarged.js +21 -0
- package/callbacks/subscriptions/postItemPickup.js +25 -0
- package/callbacks/subscriptions/postKnifeInitLate.js +20 -0
- package/callbacks/subscriptions/postLaserInitLate.js +20 -0
- package/callbacks/subscriptions/postNPCInitLate.js +20 -0
- package/callbacks/subscriptions/postNPCStateChanged.js +24 -0
- package/callbacks/subscriptions/postNewLevelReordered.js +16 -0
- package/callbacks/subscriptions/postNewRoomEarly.js +16 -0
- package/callbacks/subscriptions/postNewRoomReordered.js +16 -0
- package/callbacks/subscriptions/postPEffectUpdateReordered.js +25 -0
- package/callbacks/subscriptions/postPickupCollect.js +20 -0
- package/callbacks/subscriptions/postPickupInitFirst.js +20 -0
- package/callbacks/subscriptions/postPickupInitLate.js +20 -0
- package/callbacks/subscriptions/postPickupStateChanged.js +20 -0
- package/callbacks/subscriptions/postPitRender.js +22 -0
- package/callbacks/subscriptions/postPitUpdate.js +22 -0
- package/callbacks/subscriptions/postPlayerChangeHealth.js +25 -0
- package/callbacks/subscriptions/postPlayerChangeType.js +20 -0
- package/callbacks/subscriptions/postPlayerCollectibleAdded.js +21 -0
- package/callbacks/subscriptions/postPlayerCollectibleRemoved.js +21 -0
- package/callbacks/subscriptions/postPlayerFatalDamage.js +29 -0
- package/callbacks/subscriptions/postPlayerInitLate.js +25 -0
- package/callbacks/subscriptions/postPlayerInitReordered.js +25 -0
- package/callbacks/subscriptions/postPlayerRenderReordered.js +25 -0
- package/callbacks/subscriptions/postPlayerUpdateReordered.js +25 -0
- package/callbacks/subscriptions/postPoopRender.js +22 -0
- package/callbacks/subscriptions/postPoopUpdate.js +22 -0
- package/callbacks/subscriptions/postPressurePlateRender.js +22 -0
- package/callbacks/subscriptions/postPressurePlateUpdate.js +22 -0
- package/callbacks/subscriptions/postProjectileInitLate.js +21 -0
- package/callbacks/subscriptions/postPurchase.js +16 -0
- package/callbacks/subscriptions/postRockRender.js +22 -0
- package/callbacks/subscriptions/postRockUpdate.js +22 -0
- package/callbacks/subscriptions/postRoomClearChanged.js +20 -0
- package/callbacks/subscriptions/postSacrifice.js +25 -0
- package/callbacks/subscriptions/postSlotAnimationChanged.js +20 -0
- package/callbacks/subscriptions/postSlotDestroyed.js +26 -0
- package/callbacks/subscriptions/postSlotInit.js +20 -0
- package/callbacks/subscriptions/postSlotRender.js +20 -0
- package/callbacks/subscriptions/postSlotUpdate.js +20 -0
- package/callbacks/subscriptions/postSpikesRender.js +22 -0
- package/callbacks/subscriptions/postSpikesUpdate.js +22 -0
- package/callbacks/subscriptions/postTNTRender.js +22 -0
- package/callbacks/subscriptions/postTNTUpdate.js +22 -0
- package/callbacks/subscriptions/postTearInitLate.js +20 -0
- package/callbacks/subscriptions/postTearInitVeryLate.js +20 -0
- package/callbacks/subscriptions/postTransformation.js +20 -0
- package/callbacks/subscriptions/postTrinketBreak.js +21 -0
- package/callbacks/subscriptions/preBerserkDeath.js +26 -0
- package/callbacks/subscriptions/preCustomRevive.js +29 -0
- package/callbacks/subscriptions/preItemPickup.js +25 -0
- package/callbacks/subscriptions/preNewLevel.js +16 -0
- package/classes/DefaultMap.js +145 -0
- package/classes/ModUpgraded.js +57 -0
- package/constants.js +143 -0
- package/constantsFirstLast.js +156 -0
- package/enums/AmbushType.js +6 -0
- package/enums/CornerType.js +8 -0
- package/enums/DecorationVariant.js +11 -0
- package/enums/HealthType.js +18 -0
- package/enums/ModCallbackCustom.js +1213 -0
- package/enums/PocketItemType.js +10 -0
- package/enums/RockAltType.js +9 -0
- package/enums/SerializationType.js +7 -0
- package/enums/SlotDestructionType.js +6 -0
- package/enums/private/CopyableIsaacAPIClassType.js +9 -0
- package/enums/private/SaveDataKey.js +8 -0
- package/enums/private/SerializationBrand.js +41 -0
- package/features/characterHealthConversion.js +86 -0
- package/features/characterStats.js +52 -0
- package/features/collectibleItemPoolType.js +45 -0
- package/features/customGridEntity.js +148 -0
- package/features/customStage/backdrop.js +191 -0
- package/features/customStage/customStageConstants.js +2 -0
- package/features/customStage/exports.js +147 -0
- package/features/customStage/gridEntities.js +138 -0
- package/features/customStage/init.js +104 -0
- package/features/customStage/metadata.json +1 -0
- package/features/customStage/shadows.js +63 -0
- package/features/customStage/streakText.js +35 -0
- package/features/customStage/util.js +36 -0
- package/features/customStage/v.js +18 -0
- package/features/customStage/versusScreen.js +184 -0
- package/features/debugDisplay/debugDisplay.js +183 -0
- package/features/debugDisplay/exports.js +441 -0
- package/features/debugDisplay/v.d.ts +18 -0
- package/features/debugDisplay/v.js +61 -0
- package/features/deployJSONRoom.js +532 -0
- package/features/disableAllSound.js +63 -0
- package/features/disableInputs.js +130 -0
- package/features/extraConsoleCommands/commandsDisplay.js +236 -0
- package/features/extraConsoleCommands/commandsSubroutines.js +109 -0
- package/features/extraConsoleCommands/exports.js +53 -0
- package/features/extraConsoleCommands/init.js +239 -0
- package/features/extraConsoleCommands/listCommands.js +1081 -0
- package/features/extraConsoleCommands/v.js +25 -0
- package/features/fadeInRemover.js +51 -0
- package/features/fastReset.js +61 -0
- package/features/forgottenSwitch.js +33 -0
- package/features/pause.js +68 -0
- package/features/persistentEntities.js +120 -0
- package/features/playerInventory.js +89 -0
- package/features/ponyDetection.js +63 -0
- package/features/preventCollectibleRotation.js +81 -0
- package/features/registerHotkey.js +91 -0
- package/features/roomClearFrame.js +42 -0
- package/features/runInNFrames.js +189 -0
- package/features/runNextRoom.js +34 -0
- package/features/saveDataManager/exports.js +183 -0
- package/features/saveDataManager/load.js +69 -0
- package/features/saveDataManager/main.js +131 -0
- package/features/saveDataManager/maps.js +9 -0
- package/features/saveDataManager/merge.js +139 -0
- package/features/saveDataManager/save.js +43 -0
- package/features/saveDataManager/saveDataManagerConstants.js +4 -0
- package/features/saveDataManager/serializationBrand.js +12 -0
- package/features/sirenHelpers.js +90 -0
- package/features/stageHistory.js +43 -0
- package/features/taintedLazarusPlayers.js +92 -0
- package/featuresInitialized.js +16 -0
- package/functions/ambush.js +35 -0
- package/functions/array.js +423 -0
- package/functions/benchmark.js +28 -0
- package/functions/bitwise.js +74 -0
- package/functions/bombs.js +11 -0
- package/functions/boss.js +137 -0
- package/functions/cacheFlag.js +11 -0
- package/functions/cards.js +217 -0
- package/functions/challenges.js +9 -0
- package/functions/character.js +102 -0
- package/functions/charge.js +202 -0
- package/functions/chargeBar.js +44 -0
- package/functions/collectibleCacheFlag.js +68 -0
- package/functions/collectibleSet.js +108 -0
- package/functions/collectibleTag.js +65 -0
- package/functions/collectibles.js +469 -0
- package/functions/color.d.ts +1 -1
- package/functions/color.js +60 -0
- package/functions/debug.js +60 -0
- package/functions/deepCopy.js +361 -0
- package/functions/deepCopyTests.js +308 -0
- package/functions/direction.js +41 -0
- package/functions/doors.js +326 -0
- package/functions/easing.js +142 -0
- package/functions/eden.js +32 -0
- package/functions/effects.js +11 -0
- package/functions/entity.js +324 -0
- package/functions/entitySpecific.js +481 -0
- package/functions/entityTypes.js +6 -0
- package/functions/enums.js +151 -0
- package/functions/familiars.js +85 -0
- package/functions/flag.js +137 -0
- package/functions/flying.js +88 -0
- package/functions/globals.js +230 -0
- package/functions/gridEntity.js +488 -0
- package/functions/gridEntitySpecific.js +109 -0
- package/functions/input.js +95 -0
- package/functions/isaacAPIClass.js +48 -0
- package/functions/jsonHelpers.js +40 -0
- package/functions/jsonRoom.js +132 -0
- package/functions/kColor.d.ts +1 -1
- package/functions/kColor.js +63 -0
- package/functions/language.js +13 -0
- package/functions/level.js +20 -0
- package/functions/log.js +567 -0
- package/functions/map.js +45 -0
- package/functions/math.js +103 -0
- package/functions/mergeTests.js +213 -0
- package/functions/minimap.js +80 -0
- package/functions/nextStage.js +290 -0
- package/functions/npc.js +101 -0
- package/functions/pickupVariants.js +47 -0
- package/functions/pickups.js +219 -0
- package/functions/pills.js +160 -0
- package/functions/player.js +808 -0
- package/functions/playerCenter.js +50 -0
- package/functions/playerDataStructures.js +114 -0
- package/functions/playerHealth.js +321 -0
- package/functions/playerIndex.js +160 -0
- package/functions/pocketItems.js +124 -0
- package/functions/positionVelocity.js +136 -0
- package/functions/random.js +73 -0
- package/functions/revive.js +140 -0
- package/functions/rng.d.ts +1 -1
- package/functions/rng.js +117 -0
- package/functions/roomData.js +174 -0
- package/functions/roomGrid.js +81 -0
- package/functions/roomShape.js +99 -0
- package/functions/rooms.js +487 -0
- package/functions/run.js +53 -0
- package/functions/saveFile.js +104 -0
- package/functions/seeds.js +18 -0
- package/functions/serialization.js +52 -0
- package/functions/set.js +99 -0
- package/functions/sound.js +9 -0
- package/functions/spawnCollectible.js +66 -0
- package/functions/sprite.js +80 -0
- package/functions/stage.js +172 -0
- package/functions/string.js +37 -0
- package/functions/table.js +143 -0
- package/functions/tears.js +31 -0
- package/functions/transformations.js +101 -0
- package/functions/trinketCacheFlag.js +46 -0
- package/functions/trinketGive.js +121 -0
- package/functions/trinkets.js +169 -0
- package/functions/tstlClass.js +125 -0
- package/functions/types.js +27 -0
- package/functions/ui.js +124 -0
- package/functions/utils.js +172 -0
- package/functions/vector.js +68 -0
- package/index.js +140 -0
- package/initCustomCallbacks.js +133 -0
- package/initFeatures.js +51 -0
- package/interfaces/AddCallbackParameterCustom.js +2 -0
- package/interfaces/ChargeBarSprites.js +2 -0
- package/interfaces/Corner.js +2 -0
- package/interfaces/CustomGridEntityData.js +2 -0
- package/interfaces/CustomStage.js +2 -0
- package/interfaces/CustomStageLua.js +13 -0
- package/interfaces/JSONRoomsFile.js +2 -0
- package/interfaces/PlayerHealth.js +2 -0
- package/interfaces/PocketItemDescription.js +2 -0
- package/interfaces/SaveData.js +2 -0
- package/interfaces/TrinketSituation.js +2 -0
- package/interfaces/private/TSTLClassMetatable.js +2 -0
- package/maps/PHDPillConversions.js +20 -0
- package/maps/cardMap.js +208 -0
- package/maps/characterMap.js +86 -0
- package/maps/collectibleDescriptionMap.js +728 -0
- package/maps/collectibleNameMap.js +727 -0
- package/maps/defaultPlayerStatMap.js +14 -0
- package/maps/falsePHDPillConversions.js +34 -0
- package/maps/gridEntityTypeToBrokenStateMap.js +29 -0
- package/maps/gridEntityXMLMap.js +128 -0
- package/maps/pillEffectMap.js +88 -0
- package/maps/roomShapeToTopLeftWallGridIndexMap.js +14 -0
- package/maps/roomTypeMap.js +39 -0
- package/maps/trinketDescriptionMap.js +196 -0
- package/maps/trinketNameMap.js +195 -0
- package/objects/LRoomShapeToRectangles.js +33 -0
- package/objects/backdropTypeToRockAltType.js +66 -0
- package/objects/bossNamePNGFileNames.js +112 -0
- package/objects/bossPortraitPNGFileNames.js +115 -0
- package/objects/callbackRegisterFunctions.js +170 -0
- package/objects/cardDescriptions.js +104 -0
- package/objects/cardNames.js +104 -0
- package/objects/cardTypes.js +103 -0
- package/objects/challengeNames.js +51 -0
- package/objects/characterNames.js +46 -0
- package/objects/coinSubTypeToValue.js +13 -0
- package/objects/colors.d.ts +1 -0
- package/objects/colors.js +24 -0
- package/objects/directionNames.js +9 -0
- package/objects/directionToDegrees.js +9 -0
- package/objects/directionToVector.js +10 -0
- package/objects/doorSlotFlagToDoorSlot.js +13 -0
- package/objects/doorSlotToDirection.js +13 -0
- package/objects/doorSlotToDoorSlotFlag.js +13 -0
- package/objects/isaacAPIClassTypeToBrand.js +9 -0
- package/objects/isaacAPIClassTypeToCopyFunction.js +12 -0
- package/objects/languageNames.js +11 -0
- package/objects/oppositeDoorSlots.js +13 -0
- package/objects/pillEffectClasses.js +56 -0
- package/objects/pillEffectNames.js +55 -0
- package/objects/pillEffectTypes.js +55 -0
- package/objects/playerNamePNGFileNames.js +55 -0
- package/objects/playerPortraitPNGFileNames.js +52 -0
- package/objects/roomShapeBounds.js +46 -0
- package/objects/roomShapeCorners.js +268 -0
- package/objects/roomShapeLayoutSizes.js +39 -0
- package/objects/roomShapeToBottomRightPosition.js +22 -0
- package/objects/roomShapeToDoorSlotCoordinates.js +109 -0
- package/objects/roomShapeToDoorSlots.js +69 -0
- package/objects/roomShapeToDoorSlotsToGridIndexDelta.js +113 -0
- package/objects/roomShapeToGridWidth.js +18 -0
- package/objects/roomShapeToTopLeftPosition.js +23 -0
- package/objects/roomShapeVolumes.js +30 -0
- package/objects/roomTypeGotoPrefixes.js +33 -0
- package/objects/roomTypeNames.js +34 -0
- package/objects/serializedIsaacAPIClassTypeToIdentityFunction.js +12 -0
- package/objects/stageTypeSuffixes.js +10 -0
- package/objects/stageTypeToLetter.js +12 -0
- package/objects/transformationNames.js +18 -0
- package/objects/versusScreenBackgroundColors.js +36 -0
- package/objects/versusScreenDirtSpotColors.js +36 -0
- package/package.json +1 -1
- package/patchErrorFunctions.js +67 -0
- package/sets/LRoomShapesSet.js +8 -0
- package/sets/bossSets.js +374 -0
- package/sets/charactersThatStartWithAnActiveItemSet.js +15 -0
- package/sets/charactersWithBlackHeartFromEternalHeartSet.js +6 -0
- package/sets/charactersWithFreeDevilDealsSet.js +3 -0
- package/sets/charactersWithNoRedHeartsSet.js +16 -0
- package/sets/charactersWithNoSoulHeartsSet.js +13 -0
- package/sets/chestPickupVariantsSet.js +16 -0
- package/sets/consoleCommandsSet.js +61 -0
- package/sets/familiarsThatShootPlayerTearsSet.js +12 -0
- package/sets/lostStyleCharactersSet.js +13 -0
- package/sets/mineShaftRoomSubTypesSet.js +9 -0
- package/sets/narrowRoomShapesSet.js +8 -0
- package/sets/redHeartSubTypesSet.js +7 -0
- package/sets/sinEntityTypesSet.js +11 -0
- package/sets/singleUseActiveCollectibleTypesSet.js +12 -0
- package/sets/storyBossesSet.js +17 -0
- package/shaderCrashFix.js +17 -0
- package/types/AnyEntity.js +2 -0
- package/types/CollectibleIndex.js +2 -0
- package/types/PickingUpItem.js +38 -0
- package/types/PlayerIndex.js +2 -0
- package/types/private/IsaacAPIClass.js +2 -0
- package/types/private/SerializedIsaacAPIClass.js +2 -0
- package/types/private/TSTLClass.js +2 -0
- package/upgradeMod.js +47 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Keyboard, ModCallback } from "isaac-typescript-definitions";
|
|
2
|
+
import { DefaultMap } from "../classes/DefaultMap";
|
|
3
|
+
import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
|
|
4
|
+
import { isKeyboardPressed } from "../functions/input";
|
|
5
|
+
import { isFunction } from "../functions/types";
|
|
6
|
+
const FEATURE_NAME = "registerHotkeys";
|
|
7
|
+
/**
|
|
8
|
+
* The keys are the keyboard keys that trigger the hotkey. The values are the functions that contain
|
|
9
|
+
* the arbitrary code to run.
|
|
10
|
+
*/
|
|
11
|
+
const staticHotkeyFunctionMap = new Map();
|
|
12
|
+
/**
|
|
13
|
+
* The keys are the functions that determine what the hotkey key is. The values are the functions
|
|
14
|
+
* that contain the arbitrary code to run.
|
|
15
|
+
*/
|
|
16
|
+
const dynamicHotkeyFunctionMap = new Map();
|
|
17
|
+
const keyPressedMap = new DefaultMap(false);
|
|
18
|
+
/** @internal */
|
|
19
|
+
export function registerHotkeyInit(mod) {
|
|
20
|
+
mod.AddCallback(ModCallback.POST_RENDER, postRender); // 2
|
|
21
|
+
}
|
|
22
|
+
// ModCallback.POST_RENDER (2)
|
|
23
|
+
function postRender() {
|
|
24
|
+
for (const [keyboard, triggerFunc] of staticHotkeyFunctionMap.entries()) {
|
|
25
|
+
checkIfTriggered(keyboard, triggerFunc);
|
|
26
|
+
}
|
|
27
|
+
for (const [keyboardFunc, triggerFunc,] of dynamicHotkeyFunctionMap.entries()) {
|
|
28
|
+
const keyboard = keyboardFunc();
|
|
29
|
+
if (keyboard !== undefined) {
|
|
30
|
+
checkIfTriggered(keyboard, triggerFunc);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function checkIfTriggered(keyboard, triggerFunc) {
|
|
35
|
+
const isPressed = isKeyboardPressed(keyboard);
|
|
36
|
+
const wasPreviouslyPressed = keyPressedMap.getAndSetDefault(keyboard);
|
|
37
|
+
keyPressedMap.set(keyboard, isPressed);
|
|
38
|
+
if (isPressed && !wasPreviouslyPressed) {
|
|
39
|
+
triggerFunc();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Helper function to run arbitrary code when you press and release a specific keyboard key.
|
|
44
|
+
*
|
|
45
|
+
* This can be used to easily set up custom hotkeys to facilitate custom game features or to assist
|
|
46
|
+
* in debugging.
|
|
47
|
+
*
|
|
48
|
+
* @param keyboardOrFunc Either the key that you want to trigger the hotkey or a function that
|
|
49
|
+
* returns the key that will trigger the hotkey. Normally, you would just
|
|
50
|
+
* specify the key directly, but you can use a function for situations where
|
|
51
|
+
* the key can change (like if end-users can specify a custom hotkey using Mod
|
|
52
|
+
* Config Menu).
|
|
53
|
+
* @param triggerFunc A function containing the arbitrary code that you want to execute when the
|
|
54
|
+
* hotkey is triggered.
|
|
55
|
+
*/
|
|
56
|
+
export function registerHotkey(keyboardOrFunc, triggerFunc) {
|
|
57
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
58
|
+
if (isFunction(keyboardOrFunc)) {
|
|
59
|
+
if (dynamicHotkeyFunctionMap.has(keyboardOrFunc)) {
|
|
60
|
+
error("Failed to register a hotkey due to a custom hotkey already being defined for the submitted function.");
|
|
61
|
+
}
|
|
62
|
+
dynamicHotkeyFunctionMap.set(keyboardOrFunc, triggerFunc);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (staticHotkeyFunctionMap.has(keyboardOrFunc)) {
|
|
66
|
+
error(`Failed to register a hotkey due to a hotkey already being defined for key: Keyboard.${Keyboard[keyboardOrFunc]} (${keyboardOrFunc})`);
|
|
67
|
+
}
|
|
68
|
+
staticHotkeyFunctionMap.set(keyboardOrFunc, triggerFunc);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Helper function to remove a hotkey created with the `registerHotkey` function.
|
|
73
|
+
*
|
|
74
|
+
* @param keyboardOrFunc Equal to the value that you passed when initially registering the hotkey.
|
|
75
|
+
*/
|
|
76
|
+
export function unregisterHotkey(keyboardOrFunc) {
|
|
77
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
78
|
+
if (isFunction(keyboardOrFunc)) {
|
|
79
|
+
if (!dynamicHotkeyFunctionMap.has(keyboardOrFunc)) {
|
|
80
|
+
error("Failed to unregister a hotkey since there is no existing hotkey defined for the submitted function.");
|
|
81
|
+
}
|
|
82
|
+
dynamicHotkeyFunctionMap.delete(keyboardOrFunc);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (!staticHotkeyFunctionMap.has(keyboardOrFunc)) {
|
|
86
|
+
error(`Failed to unregister a hotkey since there is no existing hotkey defined for key: Keyboard.${Keyboard[keyboardOrFunc]} (${keyboardOrFunc})`);
|
|
87
|
+
}
|
|
88
|
+
staticHotkeyFunctionMap.delete(keyboardOrFunc);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVnaXN0ZXJIb3RrZXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wYWNrYWdlcy9pc2FhY3NjcmlwdC1jb21tb24vc3JjL2ZlYXR1cmVzL3JlZ2lzdGVySG90a2V5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDckUsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3ZFLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ3ZELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUVoRCxNQUFNLFlBQVksR0FBRyxpQkFBaUIsQ0FBQztBQUV2Qzs7O0dBR0c7QUFDSCxNQUFNLHVCQUF1QixHQUFHLElBQUksR0FBRyxFQUF3QixDQUFDO0FBRWhFOzs7R0FHRztBQUNILE1BQU0sd0JBQXdCLEdBQUcsSUFBSSxHQUFHLEVBR3JDLENBQUM7QUFFSixNQUFNLGFBQWEsR0FBRyxJQUFJLFVBQVUsQ0FBb0IsS0FBSyxDQUFDLENBQUM7QUFFL0QsZ0JBQWdCO0FBQ2hCLE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxHQUFRO0lBQ3pDLEdBQUcsQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDLFdBQVcsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDLElBQUk7QUFDNUQsQ0FBQztBQUVELDhCQUE4QjtBQUM5QixTQUFTLFVBQVU7SUFDakIsS0FBSyxNQUFNLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxJQUFJLHVCQUF1QixDQUFDLE9BQU8sRUFBRSxFQUFFO1FBQ3ZFLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsQ0FBQztLQUN6QztJQUVELEtBQUssTUFBTSxDQUNULFlBQVksRUFDWixXQUFXLEVBQ1osSUFBSSx3QkFBd0IsQ0FBQyxPQUFPLEVBQUUsRUFBRTtRQUN2QyxNQUFNLFFBQVEsR0FBRyxZQUFZLEVBQUUsQ0FBQztRQUNoQyxJQUFJLFFBQVEsS0FBSyxTQUFTLEVBQUU7WUFDMUIsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDO1NBQ3pDO0tBQ0Y7QUFDSCxDQUFDO0FBRUQsU0FBUyxnQkFBZ0IsQ0FBQyxRQUFrQixFQUFFLFdBQXVCO0lBQ25FLE1BQU0sU0FBUyxHQUFHLGlCQUFpQixDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzlDLE1BQU0sb0JBQW9CLEdBQUcsYUFBYSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3RFLGFBQWEsQ0FBQyxHQUFHLENBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBRXZDLElBQUksU0FBUyxJQUFJLENBQUMsb0JBQW9CLEVBQUU7UUFDdEMsV0FBVyxFQUFFLENBQUM7S0FDZjtBQUNILENBQUM7QUFFRDs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsTUFBTSxVQUFVLGNBQWMsQ0FDNUIsY0FBdUQsRUFDdkQsV0FBdUI7SUFFdkIsNkJBQTZCLENBQUMsWUFBWSxDQUFDLENBQUM7SUFFNUMsSUFBSSxVQUFVLENBQUMsY0FBYyxDQUFDLEVBQUU7UUFDOUIsSUFBSSx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLEVBQUU7WUFDaEQsS0FBSyxDQUNILHNHQUFzRyxDQUN2RyxDQUFDO1NBQ0g7UUFFRCx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsY0FBYyxFQUFFLFdBQVcsQ0FBQyxDQUFDO0tBQzNEO1NBQU07UUFDTCxJQUFJLHVCQUF1QixDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsRUFBRTtZQUMvQyxLQUFLLENBQ0gsdUZBQXVGLFFBQVEsQ0FBQyxjQUFjLENBQUMsS0FBSyxjQUFjLEdBQUcsQ0FDdEksQ0FBQztTQUNIO1FBRUQsdUJBQXVCLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxXQUFXLENBQUMsQ0FBQztLQUMxRDtBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQixDQUM5QixjQUF1RDtJQUV2RCw2QkFBNkIsQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUU1QyxJQUFJLFVBQVUsQ0FBQyxjQUFjLENBQUMsRUFBRTtRQUM5QixJQUFJLENBQUMsd0JBQXdCLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxFQUFFO1lBQ2pELEtBQUssQ0FDSCxxR0FBcUcsQ0FDdEcsQ0FBQztTQUNIO1FBRUQsd0JBQXdCLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0tBQ2pEO1NBQU07UUFDTCxJQUFJLENBQUMsdUJBQXVCLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxFQUFFO1lBQ2hELEtBQUssQ0FDSCw2RkFBNkYsUUFBUSxDQUFDLGNBQWMsQ0FBQyxLQUFLLGNBQWMsR0FBRyxDQUM1SSxDQUFDO1NBQ0g7UUFFRCx1QkFBdUIsQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLENBQUM7S0FDaEQ7QUFDSCxDQUFDIn0=
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { game } from "../cachedClasses";
|
|
2
|
+
import { ModCallbackCustom } from "../enums/ModCallbackCustom";
|
|
3
|
+
import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
|
|
4
|
+
import { saveDataManager } from "./saveDataManager/exports";
|
|
5
|
+
const FEATURE_NAME = "roomClearFrame";
|
|
6
|
+
const v = {
|
|
7
|
+
room: {
|
|
8
|
+
roomClearGameFrame: undefined,
|
|
9
|
+
roomClearRoomFrame: undefined,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
/** @internal */
|
|
13
|
+
export function roomClearFrameInit(mod) {
|
|
14
|
+
saveDataManager(FEATURE_NAME, v);
|
|
15
|
+
mod.AddCallbackCustom(ModCallbackCustom.POST_ROOM_CLEAR_CHANGED, postRoomClearChangedTrue, true);
|
|
16
|
+
}
|
|
17
|
+
// ModCallbackCustom.POST_ROOM_CLEAR_CHANGED
|
|
18
|
+
// true
|
|
19
|
+
function postRoomClearChangedTrue() {
|
|
20
|
+
const gameFrameCount = game.GetFrameCount();
|
|
21
|
+
const room = game.GetRoom();
|
|
22
|
+
const roomFrameCount = room.GetFrameCount();
|
|
23
|
+
v.room.roomClearGameFrame = gameFrameCount;
|
|
24
|
+
v.room.roomClearRoomFrame = roomFrameCount;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Helper function to get the game frame (i.e. `Game.GetFrameCount`) of the last time that this room
|
|
28
|
+
* was cleared. Returns undefined if the room has never been cleared.
|
|
29
|
+
*/
|
|
30
|
+
export function getRoomClearGameFrame() {
|
|
31
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
32
|
+
return v.room.roomClearGameFrame;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Helper function to get the room frame (i.e. `Room.GetFrameCount`) of the last time that this room
|
|
36
|
+
* was cleared. Returns undefined if the room has never been cleared.
|
|
37
|
+
*/
|
|
38
|
+
export function getRoomClearRoomFrame() {
|
|
39
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
40
|
+
return v.room.roomClearGameFrame;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm9vbUNsZWFyRnJhbWUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wYWNrYWdlcy9pc2FhY3NjcmlwdC1jb21tb24vc3JjL2ZlYXR1cmVzL3Jvb21DbGVhckZyYW1lLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUV4QyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsNkJBQTZCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN2RSxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFNUQsTUFBTSxZQUFZLEdBQUcsZ0JBQWdCLENBQUM7QUFFdEMsTUFBTSxDQUFDLEdBQUc7SUFDUixJQUFJLEVBQUU7UUFDSixrQkFBa0IsRUFBRSxTQUE0QjtRQUNoRCxrQkFBa0IsRUFBRSxTQUE0QjtLQUNqRDtDQUNGLENBQUM7QUFFRixnQkFBZ0I7QUFDaEIsTUFBTSxVQUFVLGtCQUFrQixDQUFDLEdBQWdCO0lBQ2pELGVBQWUsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFFakMsR0FBRyxDQUFDLGlCQUFpQixDQUNuQixpQkFBaUIsQ0FBQyx1QkFBdUIsRUFDekMsd0JBQXdCLEVBQ3hCLElBQUksQ0FDTCxDQUFDO0FBQ0osQ0FBQztBQUVELDRDQUE0QztBQUM1QyxPQUFPO0FBQ1AsU0FBUyx3QkFBd0I7SUFDL0IsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQzVDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUM1QixNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7SUFFNUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxjQUFjLENBQUM7SUFDM0MsQ0FBQyxDQUFDLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxjQUFjLENBQUM7QUFDN0MsQ0FBQztBQUVEOzs7R0FHRztBQUNILE1BQU0sVUFBVSxxQkFBcUI7SUFDbkMsNkJBQTZCLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDNUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDO0FBQ25DLENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUscUJBQXFCO0lBQ25DLDZCQUE2QixDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQzVDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQztBQUNuQyxDQUFDIn0=
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ModCallback } from "isaac-typescript-definitions";
|
|
2
|
+
import { game } from "../cachedClasses";
|
|
3
|
+
import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
|
|
4
|
+
import { arrayRemoveInPlace } from "../functions/array";
|
|
5
|
+
import { saveDataManager } from "./saveDataManager/exports";
|
|
6
|
+
const FEATURE_NAME = "runInNFrames";
|
|
7
|
+
const v = {
|
|
8
|
+
run: {
|
|
9
|
+
queuedGameFunctionTuples: [],
|
|
10
|
+
queuedRenderFunctionTuples: [],
|
|
11
|
+
intervalGameFunctionTuples: [],
|
|
12
|
+
intervalRenderFunctionTuples: [],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
/** @internal */
|
|
16
|
+
export function runInNFramesInit(mod) {
|
|
17
|
+
saveDataManager(FEATURE_NAME, v, () => false); // Functions are not serializable.
|
|
18
|
+
mod.AddCallback(ModCallback.POST_UPDATE, postUpdate); // 1
|
|
19
|
+
mod.AddCallback(ModCallback.POST_RENDER, postRender); // 2
|
|
20
|
+
}
|
|
21
|
+
// ModCallback.POST_UPDATE (1)
|
|
22
|
+
function postUpdate() {
|
|
23
|
+
const gameFrameCount = game.GetFrameCount();
|
|
24
|
+
checkExecuteQueuedFunctions(gameFrameCount, v.run.queuedGameFunctionTuples);
|
|
25
|
+
checkExecuteIntervalFunctions(gameFrameCount, v.run.intervalGameFunctionTuples);
|
|
26
|
+
}
|
|
27
|
+
// ModCallback.POST_RENDER (2)
|
|
28
|
+
function postRender() {
|
|
29
|
+
const renderFrameCount = Isaac.GetFrameCount();
|
|
30
|
+
checkExecuteQueuedFunctions(renderFrameCount, v.run.queuedRenderFunctionTuples);
|
|
31
|
+
checkExecuteIntervalFunctions(renderFrameCount, v.run.intervalRenderFunctionTuples);
|
|
32
|
+
}
|
|
33
|
+
function checkExecuteQueuedFunctions(frameCount, functionTuples) {
|
|
34
|
+
const firingFunctions = functionTuples.filter(([frameCountToFire]) => frameCount >= frameCountToFire);
|
|
35
|
+
for (const tuple of firingFunctions) {
|
|
36
|
+
const [_frameCountToFire, func] = tuple;
|
|
37
|
+
func();
|
|
38
|
+
arrayRemoveInPlace(functionTuples, tuple);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function checkExecuteIntervalFunctions(frameCount, functionTuples) {
|
|
42
|
+
const firingFunctions = functionTuples.filter(([frameCountToFire]) => frameCount >= frameCountToFire);
|
|
43
|
+
for (const tuple of firingFunctions) {
|
|
44
|
+
const [_frameCountToFire, func, numIntervalFrames] = tuple;
|
|
45
|
+
const returnValue = func();
|
|
46
|
+
arrayRemoveInPlace(functionTuples, tuple);
|
|
47
|
+
// Queue the next interval (as long as the function did not return false).
|
|
48
|
+
if (returnValue) {
|
|
49
|
+
const nextFireFrame = frameCount + numIntervalFrames;
|
|
50
|
+
const newTuple = [
|
|
51
|
+
nextFireFrame,
|
|
52
|
+
func,
|
|
53
|
+
numIntervalFrames,
|
|
54
|
+
];
|
|
55
|
+
functionTuples.push(newTuple);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Supply a function to run N game frames from now in the `POST_UPDATE` callback.
|
|
61
|
+
*
|
|
62
|
+
* For a usage example, see the documentation for the `runNextGameFrame`, which is used in a similar
|
|
63
|
+
* way.
|
|
64
|
+
*
|
|
65
|
+
* Note that this function will not handle saving and quitting. If a player saving and quitting
|
|
66
|
+
* before the deferred function fires would cause a bug in your mod, then you should handle deferred
|
|
67
|
+
* functions manually using serializable data.
|
|
68
|
+
*/
|
|
69
|
+
export function runInNGameFrames(func, gameFrames) {
|
|
70
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
71
|
+
const gameFrameCount = game.GetFrameCount();
|
|
72
|
+
const functionFireFrame = gameFrameCount + gameFrames;
|
|
73
|
+
const tuple = [functionFireFrame, func];
|
|
74
|
+
v.run.queuedGameFunctionTuples.push(tuple);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Supply a function to run N render frames from now in the `POST_RENDER` callback.
|
|
78
|
+
*
|
|
79
|
+
* For a usage example, see the documentation for the `runNextGameFrame`, which is used in a similar
|
|
80
|
+
* way.
|
|
81
|
+
*
|
|
82
|
+
* Note that this function will not handle saving and quitting. If a player saving and quitting
|
|
83
|
+
* before the deferred function fires would cause a bug in your mod, then you should handle deferred
|
|
84
|
+
* functions manually using serializable data.
|
|
85
|
+
*/
|
|
86
|
+
export function runInNRenderFrames(func, renderFrames) {
|
|
87
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
88
|
+
const renderFrameCount = Isaac.GetFrameCount();
|
|
89
|
+
const functionFireFrame = renderFrameCount + renderFrames;
|
|
90
|
+
const tuple = [functionFireFrame, func];
|
|
91
|
+
v.run.queuedRenderFunctionTuples.push(tuple);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Supply a function to run on the next `POST_UPDATE` callback.
|
|
95
|
+
*
|
|
96
|
+
* For example:
|
|
97
|
+
*
|
|
98
|
+
* ```ts
|
|
99
|
+
* const NUM_EXPLODER_EXPLOSIONS = 5;
|
|
100
|
+
*
|
|
101
|
+
* function useItemExploder(player: EntityPlayer) {
|
|
102
|
+
* playSound("exploderBegin");
|
|
103
|
+
* explode(player, NUM_EXPLODER_EXPLOSIONS);
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* function explode(player: EntityPlayer, numFramesLeft: int) {
|
|
107
|
+
* Isaac.Explode(player, undefined, 1);
|
|
108
|
+
* numFramesLeft -= 1;
|
|
109
|
+
* if (numFramesLeft === 0) {
|
|
110
|
+
* runNextFrame(() => {
|
|
111
|
+
* explode(player, numFramesLeft);
|
|
112
|
+
* });
|
|
113
|
+
* }
|
|
114
|
+
* }
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
* Note that this function will not handle saving and quitting. If a player saving and quitting
|
|
118
|
+
* before the deferred function fires would cause a bug in your mod, then you should handle deferred
|
|
119
|
+
* functions manually using serializable data.
|
|
120
|
+
*/
|
|
121
|
+
export function runNextGameFrame(func) {
|
|
122
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
123
|
+
runInNGameFrames(func, 1);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Supply a function to run on the next `POST_RENDER` callback.
|
|
127
|
+
*
|
|
128
|
+
* For a usage example, see the documentation for the `runNextGameFrame`, which is used in a similar
|
|
129
|
+
* way.
|
|
130
|
+
*
|
|
131
|
+
* Note that this function will not handle saving and quitting.
|
|
132
|
+
*/
|
|
133
|
+
export function runNextRenderFrame(func) {
|
|
134
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
135
|
+
runInNRenderFrames(func, 1);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Supply a function to be repeatedly run on an interval of N game frames in the `POST_UPDATE`
|
|
139
|
+
* callback. The function will continue to be fired until `false` is returned from the function.
|
|
140
|
+
*
|
|
141
|
+
* This is similar to the `setInterval` vanilla JavaScript function, except there is no
|
|
142
|
+
* corresponding `clearInterval` function. (Instead, the return value from the supplied function is
|
|
143
|
+
* used to stop the interval.)
|
|
144
|
+
*
|
|
145
|
+
* Note that this function will not handle saving and quitting. You must manually restart any
|
|
146
|
+
* intervals if the player saves and quits in the middle of a run.
|
|
147
|
+
*
|
|
148
|
+
* @param func The function to repeatedly run on an interval.
|
|
149
|
+
* @param gameFrames The amount of game frames to wait between each run.
|
|
150
|
+
* @param runImmediately Whether or not to execute the function right now before waiting for the
|
|
151
|
+
* interval.
|
|
152
|
+
*/
|
|
153
|
+
export function setIntervalGameFrames(func, gameFrames, runImmediately) {
|
|
154
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
155
|
+
const gameFrameCount = game.GetFrameCount();
|
|
156
|
+
const functionFireFrame = gameFrameCount + gameFrames;
|
|
157
|
+
const tuple = [functionFireFrame, func, gameFrames];
|
|
158
|
+
v.run.intervalGameFunctionTuples.push(tuple);
|
|
159
|
+
if (runImmediately) {
|
|
160
|
+
func();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Supply a function to be repeatedly run on an interval of N render frames in the `POST_RENDER`
|
|
165
|
+
* callback. The function will continue to be fired until `false` is returned from the function.
|
|
166
|
+
*
|
|
167
|
+
* This is similar to the `setInterval` vanilla JavaScript function, except there is no
|
|
168
|
+
* corresponding `clearInterval` function. (Instead, the return value from the supplied function is
|
|
169
|
+
* used to stop the interval.)
|
|
170
|
+
*
|
|
171
|
+
* Note that this function will not handle saving and quitting. You must manually restart any
|
|
172
|
+
* intervals if the player saves and quits in the middle of a run.
|
|
173
|
+
*
|
|
174
|
+
* @param func The function to repeatedly run on an interval.
|
|
175
|
+
* @param renderFrames The amount of game frames to wait between each run.
|
|
176
|
+
* @param runImmediately Whether or not to execute the function right now before waiting for the
|
|
177
|
+
* interval.
|
|
178
|
+
*/
|
|
179
|
+
export function setIntervalRenderFrames(func, renderFrames, runImmediately) {
|
|
180
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
181
|
+
const renderFrameCount = Isaac.GetFrameCount();
|
|
182
|
+
const functionFireFrame = renderFrameCount + renderFrames;
|
|
183
|
+
const tuple = [functionFireFrame, func, renderFrames];
|
|
184
|
+
v.run.intervalGameFunctionTuples.push(tuple);
|
|
185
|
+
if (runImmediately) {
|
|
186
|
+
func();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVuSW5ORnJhbWVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcGFja2FnZXMvaXNhYWNzY3JpcHQtY29tbW9uL3NyYy9mZWF0dXJlcy9ydW5Jbk5GcmFtZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQzNELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUN4QyxPQUFPLEVBQUUsNkJBQTZCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUN2RSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUN4RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFNUQsTUFBTSxZQUFZLEdBQUcsY0FBYyxDQUFDO0FBZ0JwQyxNQUFNLENBQUMsR0FBRztJQUNSLEdBQUcsRUFBRTtRQUNILHdCQUF3QixFQUFFLEVBQTJCO1FBQ3JELDBCQUEwQixFQUFFLEVBQTJCO1FBRXZELDBCQUEwQixFQUFFLEVBQTZCO1FBQ3pELDRCQUE0QixFQUFFLEVBQTZCO0tBQzVEO0NBQ0YsQ0FBQztBQUVGLGdCQUFnQjtBQUNoQixNQUFNLFVBQVUsZ0JBQWdCLENBQUMsR0FBUTtJQUN2QyxlQUFlLENBQUMsWUFBWSxFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLGtDQUFrQztJQUVqRixHQUFHLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUUsVUFBVSxDQUFDLENBQUMsQ0FBQyxJQUFJO0lBQzFELEdBQUcsQ0FBQyxXQUFXLENBQUMsV0FBVyxDQUFDLFdBQVcsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDLElBQUk7QUFDNUQsQ0FBQztBQUVELDhCQUE4QjtBQUM5QixTQUFTLFVBQVU7SUFDakIsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLGFBQWEsRUFB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
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ModCallbackCustom } from "../enums/ModCallbackCustom";
|
|
2
|
+
import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
|
|
3
|
+
import { emptyArray } from "../functions/array";
|
|
4
|
+
import { saveDataManager } from "./saveDataManager/exports";
|
|
5
|
+
const FEATURE_NAME = "runNextRoom";
|
|
6
|
+
const v = {
|
|
7
|
+
run: {
|
|
8
|
+
queuedFunctions: [],
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
/** @internal */
|
|
12
|
+
export function runNextRoomInit(mod) {
|
|
13
|
+
saveDataManager(FEATURE_NAME, v, () => false); // Functions are not serializable.
|
|
14
|
+
mod.AddCallbackCustom(ModCallbackCustom.POST_NEW_ROOM_REORDERED, postNewRoomReordered);
|
|
15
|
+
}
|
|
16
|
+
// ModCallbackCustom.POST_NEW_ROOM_REORDERED
|
|
17
|
+
function postNewRoomReordered() {
|
|
18
|
+
for (const func of v.run.queuedFunctions) {
|
|
19
|
+
func();
|
|
20
|
+
}
|
|
21
|
+
emptyArray(v.run.queuedFunctions);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Supply a function to run on the next `POST_NEW_ROOM` callback.
|
|
25
|
+
*
|
|
26
|
+
* Note that this function will not handle saving and quitting. If a player saving and quitting
|
|
27
|
+
* before the deferred function fires would cause a bug in your mod, then you should handle deferred
|
|
28
|
+
* functions manually using serializable data.
|
|
29
|
+
*/
|
|
30
|
+
export function runNextRoom(func) {
|
|
31
|
+
errorIfFeaturesNotInitialized(FEATURE_NAME);
|
|
32
|
+
v.run.queuedFunctions.push(func);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVuTmV4dFJvb20uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wYWNrYWdlcy9pc2FhY3NjcmlwdC1jb21tb24vc3JjL2ZlYXR1cmVzL3J1bk5leHRSb29tLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQy9ELE9BQU8sRUFBRSw2QkFBNkIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3ZFLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFNUQsTUFBTSxZQUFZLEdBQUcsYUFBYSxDQUFDO0FBRW5DLE1BQU0sQ0FBQyxHQUFHO0lBQ1IsR0FBRyxFQUFFO1FBQ0gsZUFBZSxFQUFFLEVBQXVCO0tBQ3pDO0NBQ0YsQ0FBQztBQUVGLGdCQUFnQjtBQUNoQixNQUFNLFVBQVUsZUFBZSxDQUFDLEdBQWdCO0lBQzlDLGVBQWUsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsa0NBQWtDO0lBRWpGLEdBQUcsQ0FBQyxpQkFBaUIsQ0FDbkIsaUJBQWlCLENBQUMsdUJBQXVCLEVBQ3pDLG9CQUFvQixDQUNyQixDQUFDO0FBQ0osQ0FBQztBQUVELDRDQUE0QztBQUM1QyxTQUFTLG9CQUFvQjtJQUMzQixLQUFLLE1BQU0sSUFBSSxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsZUFBZSxFQUFFO1FBQ3hDLElBQUksRUFBRSxDQUFDO0tBQ1I7SUFFRCxVQUFVLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUMsQ0FBQztBQUNwQyxDQUFDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxJQUFnQjtJQUMxQyw2QkFBNkIsQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUM1QyxDQUFDLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDbkMsQ0FBQyJ9
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { SerializationType } from "../../enums/SerializationType";
|
|
2
|
+
import { errorIfFeaturesNotInitialized } from "../../featuresInitialized";
|
|
3
|
+
import { deepCopy } from "../../functions/deepCopy";
|
|
4
|
+
import { isString } from "../../functions/types";
|
|
5
|
+
import { forceSaveDataManagerLoad, forceSaveDataManagerSave, restoreDefaultSaveData, } from "./main";
|
|
6
|
+
import { saveDataConditionalFuncMap, saveDataDefaultsMap, saveDataMap, } from "./maps";
|
|
7
|
+
import { SAVE_DATA_MANAGER_FEATURE_NAME } from "./saveDataManagerConstants";
|
|
8
|
+
/**
|
|
9
|
+
* This is the entry point to the save data manager, a system which provides two major features:
|
|
10
|
+
*
|
|
11
|
+
* 1. automatic resetting of variables on a new run, on a new level, or on a new room (as desired)
|
|
12
|
+
* 2. automatic saving and loading of all tracked data to the "save#.dat" file
|
|
13
|
+
*
|
|
14
|
+
* You feed this function with an anonymous object containing your variables, and then it will
|
|
15
|
+
* automatically manage them for you. (See below for an example.)
|
|
16
|
+
*
|
|
17
|
+
* The save data manager is meant to be called once for each feature of your mod. In other words,
|
|
18
|
+
* you should not put all of the data for your mod on the same object. Instead, scope your variables
|
|
19
|
+
* locally to a single file that contains a mod feature, and then call this function to register
|
|
20
|
+
* them. For example:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* // in file: feature1.ts
|
|
24
|
+
* import { saveDataManager } from "isaacscript-common";
|
|
25
|
+
*
|
|
26
|
+
* // Declare local variables for this file or feature.
|
|
27
|
+
* const v = {
|
|
28
|
+
* // These variables are never reset; manage them yourself at will.
|
|
29
|
+
* persistent: {
|
|
30
|
+
* foo1: 0,
|
|
31
|
+
* },
|
|
32
|
+
*
|
|
33
|
+
* // These variables are reset at the beginning of every run.
|
|
34
|
+
* run: {
|
|
35
|
+
* foo2: 0,
|
|
36
|
+
* },
|
|
37
|
+
*
|
|
38
|
+
* // These variables are reset at the beginning of every level.
|
|
39
|
+
* level: {
|
|
40
|
+
* foo3: 0,
|
|
41
|
+
* },
|
|
42
|
+
*
|
|
43
|
+
* // These variables are reset at the beginning of every room.
|
|
44
|
+
* room: {
|
|
45
|
+
* foo4: 0,
|
|
46
|
+
* },
|
|
47
|
+
* };
|
|
48
|
+
* // Every child object is optional; only create the ones that you need.
|
|
49
|
+
*
|
|
50
|
+
* // Register the variables with the save data manager. (We need to provide a string key that
|
|
51
|
+
* // matches the name of this file.)
|
|
52
|
+
* function feature1Init() {
|
|
53
|
+
* saveDataManager("feature1", v);
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* // Elsewhere in the file, use your variables.
|
|
57
|
+
* function feature1Function() {
|
|
58
|
+
* if (v.run.foo1 > 0) {
|
|
59
|
+
* // Insert code here.
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* - Save data is loaded from disk in the `POST_PLAYER_INIT` callback (i.e. the first callback that
|
|
65
|
+
* can possibly run).
|
|
66
|
+
* - Save data is recorded to disk in the `PRE_GAME_EXIT` callback.
|
|
67
|
+
*
|
|
68
|
+
* Note that before using the save data manager, you must call the `upgradeMod` function. (Upgrade
|
|
69
|
+
* your mod before registering any of your own callbacks so that the save data manager will run
|
|
70
|
+
* before any of your code does.)
|
|
71
|
+
*
|
|
72
|
+
* If you want the save data manager to load data before the `POST_PLAYER_INIT` callback (i.e. in
|
|
73
|
+
* the main menu), then you should explicitly call the `saveDataManagerLoad` function. (The save
|
|
74
|
+
* data manager cannot do this on its own because it cannot know when your mod features are finished
|
|
75
|
+
* initializing.)
|
|
76
|
+
*
|
|
77
|
+
* Finally, some features may have variables that need to be automatically reset per run/level, but
|
|
78
|
+
* not saved to disk on game exit. (For example, if they contain functions or other non-serializable
|
|
79
|
+
* data.) For these cases, set the second argument to `() => false`.
|
|
80
|
+
*
|
|
81
|
+
* @param key The name of the file or feature that is submitting data to be managed by the save data
|
|
82
|
+
* manager. The save data manager will throw an error if the key is already registered.
|
|
83
|
+
* @param v An object that corresponds to the `SaveData` interface. The object is conventionally
|
|
84
|
+
* called "v" for brevity. ("v" is short for "local variables").
|
|
85
|
+
* @param conditionalFunc An optional function to run upon saving this key to disk. For example,
|
|
86
|
+
* this allows features to only save data to disk if the feature is enabled.
|
|
87
|
+
* Specify a value of `() => false` to completely disable saving this feature
|
|
88
|
+
* to disk. Disabling saving to disk is useful if you are using data that is
|
|
89
|
+
* not serializable. Alternatively, it could be useful if you want to use the
|
|
90
|
+
* save data manager to automatically reset variables on run/level/room, but
|
|
91
|
+
* not clutter the the "save#.dat" file with unnecessary keys.
|
|
92
|
+
*/
|
|
93
|
+
export function saveDataManager(key, v, conditionalFunc) {
|
|
94
|
+
errorIfFeaturesNotInitialized(SAVE_DATA_MANAGER_FEATURE_NAME);
|
|
95
|
+
if (!isString(key)) {
|
|
96
|
+
error(`The ${SAVE_DATA_MANAGER_FEATURE_NAME} requires that keys are strings. You tried to use a key of type: ${typeof key}`);
|
|
97
|
+
}
|
|
98
|
+
if (saveDataMap.has(key)) {
|
|
99
|
+
error(`The ${SAVE_DATA_MANAGER_FEATURE_NAME} is already managing save data for a key of: ${key}`);
|
|
100
|
+
}
|
|
101
|
+
// Add the new save data to the map.
|
|
102
|
+
saveDataMap.set(key, v);
|
|
103
|
+
// If the only key in the save data is "room", then we don't have to worry about saving this data
|
|
104
|
+
// to disk (because the room would be reloaded upon resuming a continued run).
|
|
105
|
+
const saveDataKeys = Object.keys(v);
|
|
106
|
+
if (saveDataKeys.length === 1 && saveDataKeys[0] === "room") {
|
|
107
|
+
conditionalFunc = () => false;
|
|
108
|
+
}
|
|
109
|
+
// Make a copy of the initial save data so that we can use it to restore the default values later
|
|
110
|
+
// on.
|
|
111
|
+
const saveDataTable = v;
|
|
112
|
+
const saveDataCopy = deepCopy(saveDataTable, SerializationType.NONE, key);
|
|
113
|
+
saveDataDefaultsMap.set(key, saveDataCopy);
|
|
114
|
+
// Store the conditional function for later, if present.
|
|
115
|
+
if (conditionalFunc !== undefined) {
|
|
116
|
+
saveDataConditionalFuncMap.set(key, conditionalFunc);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The save data manager will automatically load variables from disk at the appropriate times (i.e.
|
|
121
|
+
* when a new run is started). Use this function to explicitly force the save data manager to load
|
|
122
|
+
* all of its variables from disk immediately.
|
|
123
|
+
*
|
|
124
|
+
* Obviously, doing this will overwrite the current data, so using this function can potentially
|
|
125
|
+
* result in lost state.
|
|
126
|
+
*/
|
|
127
|
+
export function saveDataManagerLoad() {
|
|
128
|
+
errorIfFeaturesNotInitialized(SAVE_DATA_MANAGER_FEATURE_NAME);
|
|
129
|
+
forceSaveDataManagerLoad();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The save data manager will automatically save variables to disk at the appropriate times (i.e.
|
|
133
|
+
* when the run is exited). Use this function to explicitly force the save data manager to write all
|
|
134
|
+
* of its variables to disk immediately.
|
|
135
|
+
*/
|
|
136
|
+
export function saveDataManagerSave() {
|
|
137
|
+
errorIfFeaturesNotInitialized(SAVE_DATA_MANAGER_FEATURE_NAME);
|
|
138
|
+
forceSaveDataManagerSave();
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* - Sets the global variable of "g" equal to all of the save data variables for this mod.
|
|
142
|
+
* - Sets the global variable of "gd" equal to all of the save data default variables for this mod.
|
|
143
|
+
*
|
|
144
|
+
* This can make debugging easier, as you can access the variables from the game's debug console.
|
|
145
|
+
* e.g. `l print(g.feature1.foo)`
|
|
146
|
+
*/
|
|
147
|
+
export function saveDataManagerSetGlobal() {
|
|
148
|
+
errorIfFeaturesNotInitialized(SAVE_DATA_MANAGER_FEATURE_NAME);
|
|
149
|
+
g = saveDataMap; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
150
|
+
gd = saveDataDefaultsMap; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The save data manager will automatically reset variables at the appropriate times (i.e. when a
|
|
154
|
+
* player enters a new room). Use this function to explicitly force the save data manager to reset a
|
|
155
|
+
* specific variable group.
|
|
156
|
+
*
|
|
157
|
+
* For example:
|
|
158
|
+
*
|
|
159
|
+
* ```ts
|
|
160
|
+
* const v = {
|
|
161
|
+
* room: {
|
|
162
|
+
* foo: 123,
|
|
163
|
+
* },
|
|
164
|
+
* };
|
|
165
|
+
*
|
|
166
|
+
* saveDataManager("file1", v);
|
|
167
|
+
*
|
|
168
|
+
* // Then, later on, to explicit reset all of the "room" variables:
|
|
169
|
+
* saveDataManagerReset("file1", "room");
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export function saveDataManagerReset(key, childObjectKey) {
|
|
173
|
+
errorIfFeaturesNotInitialized(SAVE_DATA_MANAGER_FEATURE_NAME);
|
|
174
|
+
if (!isString(key)) {
|
|
175
|
+
error(`The ${SAVE_DATA_MANAGER_FEATURE_NAME} requires that keys are strings. You tried to use a key of type: ${typeof key}`);
|
|
176
|
+
}
|
|
177
|
+
const saveData = saveDataMap.get(key);
|
|
178
|
+
if (saveData === undefined) {
|
|
179
|
+
error(`The ${SAVE_DATA_MANAGER_FEATURE_NAME} is not managing save data for a key of: ${key}`);
|
|
180
|
+
}
|
|
181
|
+
restoreDefaultSaveData(key, saveData, childObjectKey);
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwb3J0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2lzYWFjc2NyaXB0LWNvbW1vbi9zcmMvZmVhdHVyZXMvc2F2ZURhdGFNYW5hZ2VyL2V4cG9ydHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDbEUsT0FBTyxFQUFFLDZCQUE2QixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDMUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ3BELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUVqRCxPQUFPLEVBQ0wsd0JBQXdCLEVBQ3hCLHdCQUF3QixFQUN4QixzQkFBc0IsR0FDdkIsTUFBTSxRQUFRLENBQUM7QUFDaEIsT0FBTyxFQUNMLDBCQUEwQixFQUMxQixtQkFBbUIsRUFDbkIsV0FBVyxHQUNaLE1BQU0sUUFBUSxDQUFDO0FBQ2hCLE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBRTVFOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FvRkc7QUFDSCxNQUFNLFVBQVUsZUFBZSxDQUM3QixHQUFXLEVBQ1gsQ0FBVyxFQUNYLGVBQStCO0lBRS9CLDZCQUE2QixDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFFOUQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUNsQixLQUFLLENBQ0gsT0FBTyw4QkFBOEIsb0VBQW9FLE9BQU8sR0FBRyxFQUFFLENBQ3RILENBQUM7S0FDSDtJQUVELElBQUksV0FBVyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUN4QixLQUFLLENBQ0gsT0FBTyw4QkFBOEIsZ0RBQWdELEdBQUcsRUFBRSxDQUMzRixDQUFDO0tBQ0g7SUFFRCxvQ0FBb0M7SUFDcEMsV0FBVyxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFFeEIsaUdBQWlHO0lBQ2pHLDhFQUE4RTtJQUM5RSxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3BDLElBQUksWUFBWSxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksWUFBWSxDQUFDLENBQUMsQ0FBQyxLQUFLLE1BQU0sRUFBRTtRQUMzRCxlQUFlLEdBQUcsR0FBRyxFQUFFLENBQUMsS0FBSyxDQUFDO0tBQy9CO0lBRUQsaUdBQWlHO0lBQ2pHLE1BQU07SUFDTixNQUFNLGFBQWEsR0FBRyxDQUFpQyxDQUFDO0lBQ3hELE1BQU0sWUFBWSxHQUFHLFFBQVEsQ0FDM0IsYUFBYSxFQUNiLGlCQUFpQixDQUFDLElBQUksRUFDdEIsR0FBRyxDQUNRLENBQUM7SUFDZCxtQkFBbUIsQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBRTNDLHdEQUF3RDtJQUN4RCxJQUFJLGVBQWUsS0FBSyxTQUFTLEVBQUU7UUFDakMsMEJBQTBCLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxlQUFlLENBQUMsQ0FBQztLQUN0RDtBQUNILENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxVQUFVLG1CQUFtQjtJQUNqQyw2QkFBNkIsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQzlELHdCQUF3QixFQUFFLENBQUM7QUFDN0IsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsbUJBQW1CO0lBQ2pDLDZCQUE2QixDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFDOUQsd0JBQXdCLEVBQUUsQ0FBQztBQUM3QixDQUFDO0FBS0Q7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLHdCQUF3QjtJQUN0Qyw2QkFBNkIsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBRTlELENBQUMsR0FBRyxXQUFXLENBQUMsQ0FBQyx3REFBd0Q7SUFDekUsRUFBRSxHQUFHLG1CQUFtQixDQUFDLENBQUMsd0RBQXdEO0FBQ3BGLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUNILE1BQU0sVUFBVSxvQkFBb0IsQ0FDbEMsR0FBVyxFQUNYLGNBQXNCO0lBRXRCLDZCQUE2QixDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFFOUQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUNsQixLQUFLLENBQ0gsT0FBTyw4QkFBOEIsb0VBQW9FLE9BQU8sR0FBRyxFQUFFLENBQ3RILENBQUM7S0FDSDtJQUVELE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUF3QyxDQUFDO0lBQzdFLElBQUksUUFBUSxLQUFLLFNBQVMsRUFBRTtRQUMxQixLQUFLLENBQ0gsT0FBTyw4QkFBOEIsNENBQTRDLEdBQUcsRUFBRSxDQUN2RixDQUFDO0tBQ0g7SUFFRCxzQkFBc0IsQ0FBQyxHQUFHLEVBQUUsUUFBUSxFQUFFLGNBQTZCLENBQUMsQ0FBQztBQUN2RSxDQUFDIn0=
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsonDecode } from "../../functions/jsonHelpers";
|
|
2
|
+
import { log, logError } from "../../functions/log";
|
|
3
|
+
import { iterateTableInOrder } from "../../functions/table";
|
|
4
|
+
import { isString, isTable } from "../../functions/types";
|
|
5
|
+
import { merge } from "./merge";
|
|
6
|
+
import { SAVE_DATA_MANAGER_DEBUG, SAVE_DATA_MANAGER_FEATURE_NAME, } from "./saveDataManagerConstants";
|
|
7
|
+
const DEFAULT_MOD_DATA = "{}";
|
|
8
|
+
export function loadFromDisk(mod, oldSaveData) {
|
|
9
|
+
if (!mod.HasData()) {
|
|
10
|
+
// There is no "save#.dat" file for this save slot.
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// First, read the "save#.dat" file into a Lua table.
|
|
14
|
+
const jsonString = readSaveDatFile(mod);
|
|
15
|
+
const newSaveData = jsonDecode(jsonString);
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
17
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
18
|
+
log('Converted data from the "save#.dat" to a Lua table.');
|
|
19
|
+
}
|
|
20
|
+
// Second, iterate over all the fields of the new table.)
|
|
21
|
+
iterateTableInOrder(newSaveData, (key, value) => {
|
|
22
|
+
// All elements of loaded save data should have keys that are strings equal to the name of the
|
|
23
|
+
// subscriber/feature. Ignore elements with other types of keys.
|
|
24
|
+
if (!isString(key)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// All elements of loaded save data should be tables that contain fields corresponding to the
|
|
28
|
+
// SaveData interface. Ignore elements that are not tables.
|
|
29
|
+
if (!isTable(value)) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Ignore elements that represent subscriptions that no longer exist in the current save data.
|
|
33
|
+
const oldSaveDataForSubscriber = oldSaveData.get(key);
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
35
|
+
if (oldSaveDataForSubscriber === undefined) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
39
|
+
if (SAVE_DATA_MANAGER_DEBUG) {
|
|
40
|
+
log(`Merging in stored data for feature: ${key}`);
|
|
41
|
+
}
|
|
42
|
+
// We do not want to blow away the child tables of the existing map, because save data could
|
|
43
|
+
// contain out-of-date fields. Instead, merge it one field at a time in a recursive way (and
|
|
44
|
+
// convert Lua tables back to TypeScriptToLua Maps, if necessary).
|
|
45
|
+
merge(oldSaveDataForSubscriber, value, key);
|
|
46
|
+
}, SAVE_DATA_MANAGER_DEBUG);
|
|
47
|
+
log(`The ${SAVE_DATA_MANAGER_FEATURE_NAME} loaded data from the "save#.dat" file.`);
|
|
48
|
+
}
|
|
49
|
+
function readSaveDatFile(mod) {
|
|
50
|
+
const renderFrameCount = Isaac.GetFrameCount();
|
|
51
|
+
const [ok, jsonStringOrErrMsg] = pcall(tryLoadModData, mod);
|
|
52
|
+
if (!ok) {
|
|
53
|
+
logError(`Failed to read from the "save#.dat" file on render frame ${renderFrameCount}: ${jsonStringOrErrMsg}`);
|
|
54
|
+
return DEFAULT_MOD_DATA;
|
|
55
|
+
}
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
57
|
+
if (jsonStringOrErrMsg === undefined) {
|
|
58
|
+
return DEFAULT_MOD_DATA;
|
|
59
|
+
}
|
|
60
|
+
const jsonStringTrimmed = jsonStringOrErrMsg.trim();
|
|
61
|
+
if (jsonStringTrimmed === "") {
|
|
62
|
+
return DEFAULT_MOD_DATA;
|
|
63
|
+
}
|
|
64
|
+
return jsonStringTrimmed;
|
|
65
|
+
}
|
|
66
|
+
function tryLoadModData(mod) {
|
|
67
|
+
return mod.LoadData();
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3BhY2thZ2VzL2lzYWFjc2NyaXB0LWNvbW1vbi9zcmMvZmVhdHVyZXMvc2F2ZURhdGFNYW5hZ2VyL2xvYWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3pELE9BQU8sRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDcEQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDNUQsT0FBTyxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUUxRCxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ2hDLE9BQU8sRUFDTCx1QkFBdUIsRUFDdkIsOEJBQThCLEdBQy9CLE1BQU0sNEJBQTRCLENBQUM7QUFFcEMsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUM7QUFFOUIsTUFBTSxVQUFVLFlBQVksQ0FDMUIsR0FBUSxFQUNSLFdBQXVDO0lBRXZDLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLEVBQUU7UUFDbEIsbURBQW1EO1FBQ25ELE9BQU87S0FDUjtJQUVELHFEQUFxRDtJQUNyRCxNQUFNLFVBQVUsR0FBRyxlQUFlLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDeEMsTUFBTSxXQUFXLEdBQUcsVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBRTNDLHVFQUF1RTtJQUN2RSxJQUFJLHVCQUF1QixFQUFFO1FBQzNCLEdBQUcsQ0FBQyxxREFBcUQsQ0FBQyxDQUFDO0tBQzVEO0lBRUQseURBQXlEO0lBQ3pELG1CQUFtQixDQUNqQixXQUFXLEVBQ1gsQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLEVBQUU7UUFDYiw4RkFBOEY7UUFDOUYsZ0VBQWdFO1FBQ2hFLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUU7WUFDbEIsT0FBTztTQUNSO1FBRUQsNkZBQTZGO1FBQzdGLDJEQUEyRDtRQUMzRCxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFO1lBQ25CLE9BQU87U0FDUjtRQUVELDhGQUE4RjtRQUM5RixNQUFNLHdCQUF3QixHQUFHLFdBQVcsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDdEQsdUVBQXVFO1FBQ3ZFLElBQUksd0JBQXdCLEtBQUssU0FBUyxFQUFFO1lBQzFDLE9BQU87U0FDUjtRQUVELHVFQUF1RTtRQUN2RSxJQUFJLHVCQUF1QixFQUFFO1lBQzNCLEdBQUcsQ0FBQyx1Q0FBdUMsR0FBRyxFQUFFLENBQUMsQ0FBQztTQUNuRDtRQUVELDRGQUE0RjtRQUM1Riw0RkFBNEY7UUFDNUYsa0VBQWtFO1FBQ2xFLEtBQUssQ0FBQyx3QkFBb0MsRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDMUQsQ0FBQyxFQUNELHVCQUF1QixDQUN4QixDQUFDO0lBRUYsR0FBRyxDQUNELE9BQU8sOEJBQThCLHlDQUF5QyxDQUMvRSxDQUFDO0FBQ0osQ0FBQztBQUVELFNBQVMsZUFBZSxDQUFDLEdBQVE7SUFDL0IsTUFBTSxnQkFBZ0IsR0FBRyxLQUFLLENBQUMsYUFBYSxFQUFFLENBQUM7SUFFL0MsTUFBTSxDQUFDLEVBQUUsRUFBRSxrQkFBa0IsQ0FBQyxHQUFHLEtBQUssQ0FBQyxjQUFjLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDNUQsSUFBSSxDQUFDLEVBQUUsRUFBRTtRQUNQLFFBQVEsQ0FDTiw0REFBNEQsZ0JBQWdCLEtBQUssa0JBQWtCLEVBQUUsQ0FDdEcsQ0FBQztRQUNGLE9BQU8sZ0JBQWdCLENBQUM7S0FDekI7SUFFRCx1RUFBdUU7SUFDdkUsSUFBSSxrQkFBa0IsS0FBSyxTQUFTLEVBQUU7UUFDcEMsT0FBTyxnQkFBZ0IsQ0FBQztLQUN6QjtJQUVELE1BQU0saUJBQWlCLEdBQUcsa0JBQWtCLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDcEQsSUFBSSxpQkFBaUIsS0FBSyxFQUFFLEVBQUU7UUFDNUIsT0FBTyxnQkFBZ0IsQ0FBQztLQUN6QjtJQUVELE9BQU8saUJBQWlCLENBQUM7QUFDM0IsQ0FBQztBQUVELFNBQVMsY0FBYyxDQUFhLEdBQVE7SUFDMUMsT0FBTyxHQUFHLENBQUMsUUFBUSxFQUFFLENBQUM7QUFDeEIsQ0FBQyJ9
|