isaacscript-common 6.11.2 → 6.12.0

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.
Files changed (142) hide show
  1. package/dist/callbacks/postGridEntityCustomRender.d.ts +2 -0
  2. package/dist/callbacks/postGridEntityCustomRender.d.ts.map +1 -0
  3. package/dist/callbacks/postGridEntityCustomRender.lua +36 -0
  4. package/dist/callbacks/postGridEntityCustomUpdate.d.ts +2 -0
  5. package/dist/callbacks/postGridEntityCustomUpdate.d.ts.map +1 -0
  6. package/dist/callbacks/postGridEntityCustomUpdate.lua +36 -0
  7. package/dist/callbacks/postNewRoomEarly.lua +2 -2
  8. package/dist/callbacks/postPickupInitFirst.lua +1 -20
  9. package/dist/callbacks/reorderedCallbacks.d.ts +5 -5
  10. package/dist/callbacks/reorderedCallbacks.d.ts.map +1 -1
  11. package/dist/callbacks/reorderedCallbacks.lua +5 -5
  12. package/dist/callbacks/subscriptions/postGridEntityCustomRender.d.ts +6 -0
  13. package/dist/callbacks/subscriptions/postGridEntityCustomRender.d.ts.map +1 -0
  14. package/dist/callbacks/subscriptions/postGridEntityCustomRender.lua +29 -0
  15. package/dist/callbacks/subscriptions/postGridEntityCustomUpdate.d.ts +6 -0
  16. package/dist/callbacks/subscriptions/postGridEntityCustomUpdate.d.ts.map +1 -0
  17. package/dist/callbacks/subscriptions/postGridEntityCustomUpdate.lua +29 -0
  18. package/dist/constants.d.ts +1 -5
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.lua +0 -7
  21. package/dist/constantsFirstLast.d.ts +5 -1
  22. package/dist/constantsFirstLast.d.ts.map +1 -1
  23. package/dist/constantsFirstLast.lua +6 -0
  24. package/dist/enums/ModCallbackCustom.d.ts +89 -66
  25. package/dist/enums/ModCallbackCustom.d.ts.map +1 -1
  26. package/dist/enums/ModCallbackCustom.lua +62 -58
  27. package/dist/features/customGridEntity.d.ts +9 -0
  28. package/dist/features/customGridEntity.d.ts.map +1 -1
  29. package/dist/features/customGridEntity.lua +20 -0
  30. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts +2 -2
  31. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts.map +1 -1
  32. package/dist/features/customTrapdoor/customTrapdoorConstants.lua +2 -2
  33. package/dist/features/customTrapdoor/init.d.ts.map +1 -1
  34. package/dist/features/customTrapdoor/init.lua +4 -3
  35. package/dist/features/customTrapdoor/touched.d.ts.map +1 -1
  36. package/dist/features/customTrapdoor/touched.lua +51 -33
  37. package/dist/features/deployJSONRoom.d.ts +2 -2
  38. package/dist/features/deployJSONRoom.lua +2 -2
  39. package/dist/features/extraConsoleCommands/listCommands.d.ts.map +1 -1
  40. package/dist/features/extraConsoleCommands/listCommands.lua +4 -4
  41. package/dist/features/pause.d.ts +1 -1
  42. package/dist/features/pause.d.ts.map +1 -1
  43. package/dist/features/pause.lua +87 -8
  44. package/dist/features/persistentEntities.d.ts.map +1 -1
  45. package/dist/features/persistentEntities.lua +7 -7
  46. package/dist/features/pickupIndex.d.ts +19 -0
  47. package/dist/features/pickupIndex.d.ts.map +1 -0
  48. package/dist/features/pickupIndex.lua +197 -0
  49. package/dist/features/roomHistory.d.ts +24 -0
  50. package/dist/features/roomHistory.d.ts.map +1 -0
  51. package/dist/features/roomHistory.lua +89 -0
  52. package/dist/functions/collectibles.d.ts +26 -13
  53. package/dist/functions/collectibles.d.ts.map +1 -1
  54. package/dist/functions/collectibles.lua +26 -13
  55. package/dist/functions/entities.d.ts +3 -3
  56. package/dist/functions/entities.d.ts.map +1 -1
  57. package/dist/functions/entities.lua +8 -3
  58. package/dist/functions/gridEntities.d.ts +2 -2
  59. package/dist/functions/gridEntities.lua +2 -2
  60. package/dist/functions/isaacAPIClass.d.ts +64 -0
  61. package/dist/functions/isaacAPIClass.d.ts.map +1 -1
  62. package/dist/functions/isaacAPIClass.lua +84 -1
  63. package/dist/functions/pickupVariants.d.ts +2 -2
  64. package/dist/functions/pickupVariants.d.ts.map +1 -1
  65. package/dist/functions/pickupVariants.lua +2 -2
  66. package/dist/functions/playerCenter.lua +2 -2
  67. package/dist/functions/playerIndex.d.ts +0 -3
  68. package/dist/functions/playerIndex.d.ts.map +1 -1
  69. package/dist/functions/playerIndex.lua +4 -23
  70. package/dist/functions/roomData.d.ts +3 -2
  71. package/dist/functions/roomData.d.ts.map +1 -1
  72. package/dist/functions/roomData.lua +3 -2
  73. package/dist/functions/rooms.d.ts +6 -6
  74. package/dist/functions/rooms.lua +6 -6
  75. package/dist/functions/stage.d.ts +1 -0
  76. package/dist/functions/stage.d.ts.map +1 -1
  77. package/dist/functions/stage.lua +4 -0
  78. package/dist/index.d.ts +3 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.lua +23 -0
  81. package/dist/initCustomCallbacks.d.ts.map +1 -1
  82. package/dist/initCustomCallbacks.lua +6 -0
  83. package/dist/initFeatures.d.ts.map +1 -1
  84. package/dist/initFeatures.lua +6 -0
  85. package/dist/interfaces/AddCallbackParameterCustom.d.ts +4 -0
  86. package/dist/interfaces/AddCallbackParameterCustom.d.ts.map +1 -1
  87. package/dist/interfaces/RoomDescription.d.ts +14 -0
  88. package/dist/interfaces/RoomDescription.d.ts.map +1 -0
  89. package/dist/interfaces/RoomDescription.lua +2 -0
  90. package/dist/objects/callbackRegisterFunctions.d.ts.map +1 -1
  91. package/dist/objects/callbackRegisterFunctions.lua +6 -0
  92. package/dist/types/CollectibleIndex.d.ts +1 -1
  93. package/dist/types/PickupIndex.d.ts +17 -0
  94. package/dist/types/PickupIndex.d.ts.map +1 -0
  95. package/dist/types/PickupIndex.lua +2 -0
  96. package/dist/types/PlayerIndex.d.ts +1 -1
  97. package/dist/upgradeMod.d.ts.map +1 -1
  98. package/package.json +1 -1
  99. package/src/callbacks/postGridEntityCustomRender.ts +44 -0
  100. package/src/callbacks/postGridEntityCustomUpdate.ts +44 -0
  101. package/src/callbacks/postNewRoomEarly.ts +2 -2
  102. package/src/callbacks/postPickupInitFirst.ts +1 -32
  103. package/src/callbacks/postPlayerReorderedCallbacks.ts +3 -3
  104. package/src/callbacks/reorderedCallbacks.ts +9 -8
  105. package/src/callbacks/subscriptions/postGridEntityCustomRender.ts +41 -0
  106. package/src/callbacks/subscriptions/postGridEntityCustomUpdate.ts +41 -0
  107. package/src/constants.ts +1 -9
  108. package/src/constantsFirstLast.ts +16 -0
  109. package/src/enums/ModCallbackCustom.ts +33 -8
  110. package/src/features/customGridEntity.ts +25 -0
  111. package/src/features/customTrapdoor/customTrapdoorConstants.ts +2 -2
  112. package/src/features/customTrapdoor/init.ts +7 -5
  113. package/src/features/customTrapdoor/touched.ts +59 -39
  114. package/src/features/deployJSONRoom.ts +4 -4
  115. package/src/features/extraConsoleCommands/listCommands.ts +5 -7
  116. package/src/features/pause.ts +97 -7
  117. package/src/features/persistentEntities.ts +9 -8
  118. package/src/features/pickupIndex.ts +257 -0
  119. package/src/features/playerInventory.ts +2 -2
  120. package/src/features/roomHistory.ts +113 -0
  121. package/src/features/saveDataManager/main.ts +2 -2
  122. package/src/features/taintedLazarusPlayers.ts +5 -5
  123. package/src/functions/collectibles.ts +26 -13
  124. package/src/functions/entities.ts +6 -3
  125. package/src/functions/gridEntities.ts +2 -2
  126. package/src/functions/isaacAPIClass.ts +106 -1
  127. package/src/functions/pickupVariants.ts +2 -2
  128. package/src/functions/playerCenter.ts +2 -2
  129. package/src/functions/playerIndex.ts +8 -21
  130. package/src/functions/roomData.ts +3 -2
  131. package/src/functions/rooms.ts +6 -6
  132. package/src/functions/stage.ts +10 -1
  133. package/src/index.ts +3 -0
  134. package/src/initCustomCallbacks.ts +4 -0
  135. package/src/initFeatures.ts +4 -0
  136. package/src/interfaces/AddCallbackParameterCustom.ts +4 -0
  137. package/src/interfaces/RoomDescription.ts +19 -0
  138. package/src/objects/callbackRegisterFunctions.ts +6 -0
  139. package/src/types/CollectibleIndex.ts +1 -1
  140. package/src/types/PickupIndex.ts +15 -0
  141. package/src/types/PlayerIndex.ts +1 -1
  142. package/src/upgradeMod.ts +2 -1
package/src/constants.ts CHANGED
@@ -2,12 +2,10 @@ import {
2
2
  CollectibleType,
3
3
  Dimension,
4
4
  ItemPoolType,
5
- LevelStage,
6
- RoomType,
7
5
  TrinketSlot,
8
6
  } from "isaac-typescript-definitions";
9
7
  import { NUM_NORMAL_PILL_COLORS } from "./constantsFirstLast";
10
- import { getEnumLength, getLastEnumValue } from "./functions/enums";
8
+ import { getEnumLength } from "./functions/enums";
11
9
 
12
10
  /**
13
11
  * The distance of the laser when Azazel does not have any range up items yet. For more info, see
@@ -112,9 +110,6 @@ export const MIN_PLAYER_SHOT_SPEED_STAT = 0.6;
112
110
  /** If you set `EntityPlayer.Speed` lower than this value, it will have no effect. */
113
111
  export const MIN_PLAYER_SPEED_STAT = 0.1;
114
112
 
115
- export const FIRST_ROOM_TYPE = RoomType.DEFAULT;
116
- export const LAST_ROOM_TYPE = getLastEnumValue(RoomType);
117
-
118
113
  /**
119
114
  * The maximum speed stat that a player can have. Any additional speed beyond this will not take
120
115
  * effect.
@@ -133,9 +128,6 @@ export const NEW_FLOOR_STARTING_POSITION_GREED_MODE = Vector(320, 280);
133
128
  */
134
129
  export const NEW_RUN_PLAYER_STARTING_POSITION = Vector(320, 380);
135
130
 
136
- export const FIRST_STAGE = LevelStage.BASEMENT_1;
137
- export const LAST_STAGE = getLastEnumValue(LevelStage);
138
-
139
131
  /** Corresponds to the maximum value for `EntityPlayer.SamsonBerserkCharge`. */
140
132
  export const MAX_TAINTED_SAMSON_BERSERK_CHARGE = 100000;
141
133
 
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  Card,
3
3
  CollectibleType,
4
+ LevelStage,
4
5
  PillColor,
5
6
  PillEffect,
6
7
  PlayerType,
8
+ RoomType,
7
9
  TrinketType,
8
10
  } from "isaac-typescript-definitions";
9
11
  import { itemConfig } from "./cachedClasses";
@@ -208,3 +210,17 @@ export const LAST_VANILLA_CHARACTER = getLastEnumValue(PlayerType);
208
210
  */
209
211
  export const FIRST_MODDED_CHARACTER = ((LAST_VANILLA_CHARACTER as int) +
210
212
  1) as PlayerType;
213
+
214
+ // ----------
215
+ // Room Types
216
+ // ----------
217
+
218
+ export const FIRST_ROOM_TYPE = RoomType.DEFAULT;
219
+ export const LAST_ROOM_TYPE = getLastEnumValue(RoomType);
220
+
221
+ // ------
222
+ // Stages
223
+ // ------
224
+
225
+ export const FIRST_STAGE = LevelStage.BASEMENT_1;
226
+ export const LAST_STAGE = getLastEnumValue(LevelStage);
@@ -336,7 +336,7 @@ export enum ModCallbackCustom {
336
336
  POST_GREED_MODE_WAVE,
337
337
 
338
338
  /**
339
- * Fires from the `POST_UPDATE` update when a grid entity changes to a state that corresponds to
339
+ * Fires from the `POST_UPDATE` callback when a grid entity changes to a state that corresponds to
340
340
  * the broken state for the respective grid entity type.
341
341
  *
342
342
  * When registering the callback, takes an optional second argument that will make the callback
@@ -363,6 +363,38 @@ export enum ModCallbackCustom {
363
363
  */
364
364
  POST_GRID_ENTITY_COLLISION,
365
365
 
366
+ /**
367
+ * Fires from the `POST_RENDER` callback on every frame that a grid entity created with the
368
+ * `spawnCustomGridEntity` helper function exists.
369
+ *
370
+ * When registering the callback, takes an optional second argument that will make the callback
371
+ * only fire if it matches the `GridEntityType` provided.
372
+ *
373
+ * ```ts
374
+ * function postGridEntityRenderCustom(
375
+ * gridEntity: GridEntity,
376
+ * gridEntityTypeCustom: GridEntityType,
377
+ * ): void {}
378
+ * ```
379
+ */
380
+ POST_GRID_ENTITY_CUSTOM_RENDER,
381
+
382
+ /**
383
+ * Fires from the `POST_UPDATE` callback on every frame that a grid entity created with the
384
+ * `spawnCustomGridEntity` helper function exists.
385
+ *
386
+ * When registering the callback, takes an optional second argument that will make the callback
387
+ * only fire if it matches the `GridEntityType` provided.
388
+ *
389
+ * ```ts
390
+ * function postGridEntityUpdateCustom(
391
+ * gridEntity: GridEntity,
392
+ * gridEntityTypeCustom: GridEntityType,
393
+ * ): void {}
394
+ * ```
395
+ */
396
+ POST_GRID_ENTITY_CUSTOM_UPDATE,
397
+
366
398
  /**
367
399
  * Fires when a new grid entity is initialized. Specifically, this is either:
368
400
  *
@@ -664,13 +696,6 @@ export enum ModCallbackCustom {
664
696
  * This callback is useful because pickups will despawn upon leaving the room and respawn upon
665
697
  * re-entering the room.
666
698
  *
667
- * For most cases, this callback will simply fire when `POST_PICKUP_INIT` fires and the player is
668
- * not re-entering a previously visited room.
669
- *
670
- * The special case is when a player enters a post-Ascent Treasure Room or Boss Room. For these
671
- * cases, the `InitSeed` of any pickups seen from previous floors is kept track of to prevent the
672
- * callback from firing when re-entering the room.
673
- *
674
699
  * When registering the callback, takes an optional second argument that will make the callback
675
700
  * only fire if the collectible type matches the `PickupVariant` provided.
676
701
  *
@@ -274,3 +274,28 @@ export function removeCustomGrid(
274
274
 
275
275
  return decoration;
276
276
  }
277
+
278
+ /**
279
+ * Helper function to get the custom grid entities in the current room. Returns an array of tuples
280
+ * containing the raw decoration grid entity and the associated entity data.
281
+ */
282
+ export function getCustomGridEntities(): Array<
283
+ [gridEntity: GridEntity, data: CustomGridEntityData]
284
+ > {
285
+ const roomListIndex = getRoomListIndex();
286
+ const roomCustomGridEntities = v.level.customGridEntities.get(roomListIndex);
287
+ if (roomCustomGridEntities === undefined) {
288
+ return [];
289
+ }
290
+
291
+ const room = game.GetRoom();
292
+ const customGridEntities: Array<[GridEntity, CustomGridEntityData]> = [];
293
+ for (const [gridIndex, data] of roomCustomGridEntities.entries()) {
294
+ const gridEntity = room.GetGridEntity(gridIndex);
295
+ if (gridEntity !== undefined) {
296
+ customGridEntities.push([gridEntity, data]);
297
+ }
298
+ }
299
+
300
+ return customGridEntities;
301
+ }
@@ -19,5 +19,5 @@ export const ANIMATIONS_THAT_PREVENT_STAGE_TRAVEL: ReadonlySet<string> =
19
19
 
20
20
  export const PIXELATION_TO_BLACK_FRAMES = 52;
21
21
 
22
- export const OTHER_PLAYER_TRAPDOOR_JUMP_DELAY_GAME_FRAMES = 10;
23
- export const OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES = 8;
22
+ export const OTHER_PLAYER_TRAPDOOR_JUMP_DELAY_GAME_FRAMES = 6;
23
+ export const OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES = 5;
@@ -21,6 +21,7 @@ import { saveDataManager } from "../saveDataManager/exports";
21
21
  import { drawBlackSprite } from "./blackSprite";
22
22
  import {
23
23
  CUSTOM_TRAPDOOR_FEATURE_NAME,
24
+ GridEntityTypeCustom,
24
25
  PIXELATION_TO_BLACK_FRAMES,
25
26
  } from "./customTrapdoorConstants";
26
27
  import { checkCustomTrapdoorOpenClose } from "./openClose";
@@ -33,8 +34,9 @@ export function customTrapdoorInit(mod: ModUpgraded): void {
33
34
  mod.AddCallback(ModCallback.POST_RENDER, postRender); // 2
34
35
  mod.AddCallback(ModCallback.POST_PEFFECT_UPDATE, postPEffectUpdate); // 4
35
36
  mod.AddCallbackCustom(
36
- ModCallbackCustom.POST_GRID_ENTITY_UPDATE,
37
- postGridEntityUpdateTrapdoor,
37
+ ModCallbackCustom.POST_GRID_ENTITY_CUSTOM_UPDATE,
38
+ postGridEntityCustomUpdateTrapdoor,
39
+ GridEntityTypeCustom.TRAPDOOR_CUSTOM,
38
40
  );
39
41
  }
40
42
 
@@ -198,9 +200,9 @@ function checkJumpComplete(player: EntityPlayer) {
198
200
  }
199
201
  }
200
202
 
201
- // ModCallbackCustom.POST_GRID_ENTITY_UPDATE
202
- // GridEntityType.TRAPDOOR (17)
203
- function postGridEntityUpdateTrapdoor(gridEntity: GridEntity) {
203
+ // ModCallbackCustom.POST_GRID_ENTITY_CUSTOM_UPDATE
204
+ // GridEntityTypeCustom.TRAPDOOR_CUSTOM
205
+ function postGridEntityCustomUpdateTrapdoor(gridEntity: GridEntity) {
204
206
  const roomListIndex = getRoomListIndex();
205
207
  const gridIndex = gridEntity.GetGridIndex();
206
208
 
@@ -1,20 +1,23 @@
1
1
  import {
2
+ ButtonAction,
2
3
  EntityCollisionClass,
4
+ EntityGridCollisionClass,
3
5
  EntityPartition,
4
6
  PlayerType,
5
7
  } from "isaac-typescript-definitions";
6
8
  import { VectorZero } from "../../constants";
7
9
  import { StageTravelState } from "../../enums/private/StageTravelState";
10
+ import { easeOutSine } from "../../functions/easing";
8
11
  import {
12
+ getAllPlayers,
9
13
  getOtherPlayers,
10
- getPlayers,
11
14
  isChildPlayer,
12
15
  } from "../../functions/playerIndex";
13
16
  import { isCharacter } from "../../functions/players";
14
17
  import { CustomTrapdoorDescription } from "../../interfaces/private/CustomTrapdoorDescription";
15
- import { disableAllInputs } from "../disableInputs";
18
+ import { disableAllInputsExceptFor } from "../disableInputs";
16
19
  import { isPlayerUsingPony } from "../ponyDetection";
17
- import { runInNGameFrames, runNextGameFrame } from "../runInNFrames";
20
+ import { runInNGameFrames, runNextRenderFrame } from "../runInNFrames";
18
21
  import {
19
22
  ANIMATIONS_THAT_PREVENT_STAGE_TRAVEL,
20
23
  CUSTOM_TRAPDOOR_FEATURE_NAME,
@@ -78,7 +81,8 @@ function playerTouchedCustomTrapdoor(
78
81
  v.run.state = StageTravelState.PLAYERS_JUMPING_DOWN;
79
82
  v.run.destination = trapdoorDescription.destination;
80
83
 
81
- disableAllInputs(CUSTOM_TRAPDOOR_FEATURE_NAME);
84
+ const whitelist = new Set([ButtonAction.CONSOLE]);
85
+ disableAllInputsExceptFor(CUSTOM_TRAPDOOR_FEATURE_NAME, whitelist);
82
86
  setPlayerAttributes(player, gridEntity.Position);
83
87
  dropTaintedForgotten(player);
84
88
 
@@ -95,6 +99,35 @@ function playerTouchedCustomTrapdoor(
95
99
  });
96
100
  }
97
101
 
102
+ function setPlayerAttributes(trapdoorPlayer: EntityPlayer, position: Vector) {
103
+ // Snap the player to the exact position of the trapdoor so that they cleanly jump down the hole.
104
+ trapdoorPlayer.Position = position;
105
+
106
+ for (const player of getAllPlayers()) {
107
+ // Disable the controls to prevent the player from moving, shooting, and so on. (We also disable
108
+ // the inputs in the `INPUT_ACTION` callback, but that does not prevent mouse inputs.)
109
+ player.ControlsEnabled = false;
110
+
111
+ // Freeze all players.
112
+ player.Velocity = VectorZero;
113
+
114
+ // We don't want enemy attacks to move the players.
115
+ player.EntityCollisionClass = EntityCollisionClass.NONE;
116
+ player.GridCollisionClass = EntityGridCollisionClass.NONE;
117
+
118
+ player.SubType = -1;
119
+ }
120
+ }
121
+
122
+ function dropTaintedForgotten(player: EntityPlayer) {
123
+ if (isCharacter(player, PlayerType.THE_FORGOTTEN_B)) {
124
+ const taintedSoul = player.GetOtherTwin();
125
+ if (taintedSoul !== undefined) {
126
+ taintedSoul.ThrowHeldEntity(VectorZero);
127
+ }
128
+ }
129
+ }
130
+
98
131
  function startDelayedJump(entityPtr: EntityPtr, trapdoorPosition: Vector) {
99
132
  const entity = entityPtr.Ref;
100
133
  if (entity === undefined) {
@@ -108,14 +141,18 @@ function startDelayedJump(entityPtr: EntityPtr, trapdoorPosition: Vector) {
108
141
 
109
142
  player.PlayExtraAnimation("Trapdoor");
110
143
 
111
- adjustPlayerVelocityToTrapdoor(entityPtr, player.Position, trapdoorPosition);
144
+ adjustPlayerPositionToTrapdoor(entityPtr, player.Position, trapdoorPosition);
112
145
  }
113
146
 
114
- function adjustPlayerVelocityToTrapdoor(
147
+ function adjustPlayerPositionToTrapdoor(
115
148
  entityPtr: EntityPtr,
116
149
  startPos: Vector,
117
150
  endPos: Vector,
118
151
  ) {
152
+ if (v.run.state !== StageTravelState.PLAYERS_JUMPING_DOWN) {
153
+ return;
154
+ }
155
+
119
156
  const entity = entityPtr.Ref;
120
157
  if (entity === undefined) {
121
158
  return;
@@ -126,50 +163,33 @@ function adjustPlayerVelocityToTrapdoor(
126
163
  return;
127
164
  }
128
165
 
166
+ runNextRenderFrame(() => {
167
+ adjustPlayerPositionToTrapdoor(entityPtr, startPos, endPos);
168
+ });
169
+
129
170
  const sprite = player.GetSprite();
130
171
  if (sprite.IsFinished("Trapdoor")) {
172
+ player.Position = endPos;
173
+ player.Velocity = VectorZero;
131
174
  return;
132
175
  }
133
176
 
134
177
  const frame = sprite.GetFrame();
135
- if (frame > OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES) {
178
+ if (frame >= OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES) {
136
179
  // We have already arrived at the trapdoor.
180
+ player.Position = endPos;
181
+ player.Velocity = VectorZero;
137
182
  return;
138
183
  }
139
184
 
185
+ // Make the player jump towards the trapdoor. We use an easing function so that the distance
186
+ // traveled is not linear, emulating what the game does.
140
187
  const totalDifference = endPos.sub(startPos);
141
- const differencePerFrame = totalDifference.div(
142
- OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES,
143
- );
144
- const differenceForThisFrame = differencePerFrame.mul(frame + 1);
188
+ const progress = frame / OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES;
189
+ const easeProgress = easeOutSine(progress);
190
+ const differenceForThisFrame = totalDifference.mul(easeProgress);
145
191
  const targetPosition = startPos.add(differenceForThisFrame);
146
- const calculatedVelocity = player.Position.sub(targetPosition);
147
-
148
- player.Velocity = calculatedVelocity;
149
-
150
- runNextGameFrame(() => {
151
- adjustPlayerVelocityToTrapdoor(entityPtr, startPos, endPos);
152
- });
153
- }
154
192
 
155
- function setPlayerAttributes(trapdoorPlayer: EntityPlayer, position: Vector) {
156
- // Snap the player to the exact position of the trapdoor so that they cleanly jump down the hole.
157
- trapdoorPlayer.Position = position;
158
-
159
- for (const player of getPlayers()) {
160
- // Freeze all players.
161
- player.Velocity = VectorZero;
162
-
163
- // We don't want enemy attacks to move the players.
164
- player.EntityCollisionClass = EntityCollisionClass.NONE;
165
- }
166
- }
167
-
168
- function dropTaintedForgotten(player: EntityPlayer) {
169
- if (isCharacter(player, PlayerType.THE_FORGOTTEN_B)) {
170
- const taintedSoul = player.GetOtherTwin();
171
- if (taintedSoul !== undefined) {
172
- taintedSoul.ThrowHeldEntity(VectorZero);
173
- }
174
- }
193
+ player.Position = targetPosition;
194
+ player.Velocity = VectorZero;
175
195
  }
@@ -225,7 +225,7 @@ function respawnPersistentEntities() {
225
225
  * Specifically, this will clear the current room of all entities and grid entities, and then spawn
226
226
  * all of the entries and grid entities in the provided JSON room.
227
227
  *
228
- * This function is meant to be used in the PostNewRoom callback.
228
+ * This function is meant to be used in the `POST_NEW_ROOM` callback.
229
229
  *
230
230
  * For example:
231
231
  *
@@ -277,7 +277,7 @@ export function deployJSONRoom(
277
277
  * Specifically, this will clear the current room of all entities and grid entities, and then spawn
278
278
  * all of the entries and grid entities in one of the provided JSON rooms.
279
279
  *
280
- * This function is meant to be used in the PostNewRoom callback.
280
+ * This function is meant to be used in the `POST_NEW_ROOM` callback.
281
281
  *
282
282
  * Note that this function does not simply choose a random element in the provided array; it will
283
283
  * properly account for each room weight using the algorithm from:
@@ -355,8 +355,8 @@ export function emptyRoom(fillWithDecorations: boolean): void {
355
355
  }
356
356
 
357
357
  /**
358
- * We remove entities in the PostNewRoom callback instead of in the PreRoomEntitySpawn callback so
359
- * that they will not re-appear when we re-enter the room.
358
+ * We remove entities in the `POST_NEW_ROOM` callback instead of in the PreRoomEntitySpawn callback
359
+ * so that they will not re-appear when we re-enter the room.
360
360
  */
361
361
  function removeSpecificNPCs() {
362
362
  const room = game.GetRoom();
@@ -33,19 +33,17 @@ import {
33
33
  TrinketType,
34
34
  } from "isaac-typescript-definitions";
35
35
  import { game, sfxManager } from "../../cachedClasses";
36
- import {
37
- FIRST_ROOM_TYPE,
38
- FIRST_STAGE,
39
- LAST_ROOM_TYPE,
40
- LAST_STAGE,
41
- MAX_LEVEL_GRID_INDEX,
42
- } from "../../constants";
36
+ import { MAX_LEVEL_GRID_INDEX } from "../../constants";
43
37
  import {
44
38
  FIRST_CARD,
45
39
  FIRST_CHARACTER,
46
40
  FIRST_PILL_EFFECT,
41
+ FIRST_ROOM_TYPE,
42
+ FIRST_STAGE,
47
43
  LAST_CARD,
48
44
  LAST_PILL_EFFECT,
45
+ LAST_ROOM_TYPE,
46
+ LAST_STAGE,
49
47
  LAST_VANILLA_CHARACTER,
50
48
  } from "../../constantsFirstLast";
51
49
  import { HealthType } from "../../enums/HealthType";
@@ -4,17 +4,36 @@ import {
4
4
  InputHook,
5
5
  ModCallback,
6
6
  } from "isaac-typescript-definitions";
7
+ import { VectorZero } from "../constants";
8
+ import {
9
+ getProjectiles,
10
+ getTears,
11
+ removeAllProjectiles,
12
+ removeAllTears,
13
+ } from "../functions/entitiesSpecific";
14
+ import { isTear } from "../functions/isaacAPIClass";
7
15
  import { logError } from "../functions/log";
16
+ import { getAllPlayers } from "../functions/playerIndex";
8
17
  import { useActiveItemTemp } from "../functions/players";
9
18
  import { disableAllInputsExceptFor, enableAllInputs } from "./disableInputs";
10
19
  import { saveDataManager } from "./saveDataManager/exports";
11
20
 
12
21
  const FEATURE_NAME = "pause";
13
22
 
23
+ interface InitialDescription {
24
+ position: Vector;
25
+ positionOffset: Vector;
26
+ velocity: Vector;
27
+ height: float;
28
+ fallingSpeed: float;
29
+ fallingAcceleration: float;
30
+ }
31
+
14
32
  const v = {
15
33
  run: {
16
34
  isPseudoPaused: false,
17
35
  shouldUnpause: false,
36
+ initialDescriptions: new Map<PtrHash, InitialDescription>(),
18
37
  },
19
38
  };
20
39
 
@@ -37,8 +56,34 @@ function postUpdate() {
37
56
  return;
38
57
  }
39
58
 
40
- const player = Isaac.GetPlayer();
41
- useActiveItemTemp(player, CollectibleType.PAUSE);
59
+ const firstPlayer = Isaac.GetPlayer();
60
+ useActiveItemTemp(firstPlayer, CollectibleType.PAUSE);
61
+
62
+ stopTearsAndProjectilesFromMoving();
63
+ }
64
+
65
+ function stopTearsAndProjectilesFromMoving() {
66
+ const tearsAndProjectiles = [...getTears(), ...getProjectiles()];
67
+
68
+ for (const tearOrProjectile of tearsAndProjectiles) {
69
+ const ptrHash = GetPtrHash(tearOrProjectile);
70
+ const initialDescription = v.run.initialDescriptions.get(ptrHash);
71
+ if (initialDescription === undefined) {
72
+ continue;
73
+ }
74
+
75
+ tearOrProjectile.Position = initialDescription.position;
76
+ tearOrProjectile.PositionOffset = initialDescription.positionOffset;
77
+ tearOrProjectile.Velocity = VectorZero;
78
+ tearOrProjectile.Height = initialDescription.height;
79
+ tearOrProjectile.FallingSpeed = 0;
80
+ if (isTear(tearOrProjectile)) {
81
+ tearOrProjectile.FallingAcceleration =
82
+ initialDescription.fallingAcceleration;
83
+ } else {
84
+ tearOrProjectile.FallingAccel = initialDescription.fallingAcceleration;
85
+ }
86
+ }
42
87
  }
43
88
 
44
89
  // ModCallback.INPUT_ACTION (13)
@@ -57,8 +102,8 @@ function inputActionGetActionValue(
57
102
  }
58
103
  v.run.shouldUnpause = false;
59
104
 
60
- // Returning a value of 1 for a single frame will be enough for the game to register an unpause
61
- // but not enough for a tear to actually be fired.
105
+ // Returning a value of 1 for a single sub-frame will be enough for the game to register an
106
+ // unpause but not enough for a tear to actually be fired.
62
107
  return 1;
63
108
  }
64
109
 
@@ -68,7 +113,7 @@ function inputActionGetActionValue(
68
113
  *
69
114
  * Under the hood, this function:
70
115
  * - uses the Pause collectible on every game frame
71
- * - disables any player inputs (except for `ButtonAction.MENU_CONFIRM`)
116
+ * - disables any player inputs (except for `ButtonAction.MENU_CONFIRM` and `ButtonAction.CONSOLE`)
72
117
  */
73
118
  export function pause(): void {
74
119
  if (v.run.isPseudoPaused) {
@@ -79,8 +124,43 @@ export function pause(): void {
79
124
  }
80
125
  v.run.isPseudoPaused = true;
81
126
 
82
- const whitelist = new Set([ButtonAction.MENU_CONFIRM]);
127
+ // Tears/projectiles in the room will move slightly on every frame, even when the Pause
128
+ // collectible is active. Thus, we manually reset the initial positions and heights on every
129
+ // frame.
130
+ v.run.initialDescriptions.clear();
131
+ const tearsAndProjectiles = [...getTears(), ...getProjectiles()];
132
+ for (const tearOrProjectile of tearsAndProjectiles) {
133
+ const ptrHash = GetPtrHash(tearOrProjectile);
134
+ const initialDescription: InitialDescription = {
135
+ position: tearOrProjectile.Position,
136
+ positionOffset: tearOrProjectile.PositionOffset,
137
+ velocity: tearOrProjectile.Velocity,
138
+ height: tearOrProjectile.Height,
139
+ fallingSpeed: tearOrProjectile.FallingSpeed,
140
+ fallingAcceleration: isTear(tearOrProjectile)
141
+ ? tearOrProjectile.FallingAcceleration
142
+ : tearOrProjectile.FallingAccel,
143
+ };
144
+ v.run.initialDescriptions.set(ptrHash, initialDescription);
145
+ }
146
+
147
+ const firstPlayer = Isaac.GetPlayer();
148
+ useActiveItemTemp(firstPlayer, CollectibleType.PAUSE);
149
+
150
+ const whitelist = new Set([ButtonAction.MENU_CONFIRM, ButtonAction.CONSOLE]);
83
151
  disableAllInputsExceptFor(FEATURE_NAME, whitelist);
152
+
153
+ for (const player of getAllPlayers()) {
154
+ // Disable the controls to prevent the players from moving, shooting, and so on. (We also
155
+ // disable the inputs in the `INPUT_ACTION` callback, but that does not prevent mouse inputs.)
156
+ player.ControlsEnabled = false;
157
+
158
+ // Prevent the players from leaving the room. (If we don't reset the velocity, they can continue
159
+ // to move towards a door.)
160
+ player.Velocity = VectorZero;
161
+ }
162
+
163
+ stopTearsAndProjectilesFromMoving();
84
164
  }
85
165
 
86
166
  /** Helper function to put things back to normal after the `pause` function was used. */
@@ -92,7 +172,17 @@ export function unpause(): void {
92
172
  return;
93
173
  }
94
174
  v.run.isPseudoPaused = false;
175
+ v.run.shouldUnpause = true;
95
176
 
96
177
  enableAllInputs(FEATURE_NAME);
97
- v.run.shouldUnpause = true;
178
+ for (const player of getAllPlayers()) {
179
+ player.ControlsEnabled = true;
180
+ }
181
+
182
+ // After a vanilla pause, the tears will not resume their normal velocity and will "stick" to the
183
+ // air. Even if we try to help the tears along by explicitly resetting all of the velocity-related
184
+ // variables to their initial values, this will not make a difference. Thus, revert to removing
185
+ // all of the tears and projectiles in the room.
186
+ removeAllTears();
187
+ removeAllProjectiles();
98
188
  }
@@ -3,12 +3,12 @@ import {
3
3
  EntityType,
4
4
  ModCallback,
5
5
  } from "isaac-typescript-definitions";
6
- import { game } from "../cachedClasses";
7
6
  import { ModUpgraded } from "../classes/ModUpgraded";
8
7
  import { ModCallbackCustom } from "../enums/ModCallbackCustom";
9
8
  import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
10
9
  import { spawn } from "../functions/entities";
11
10
  import { getRoomListIndex } from "../functions/roomData";
11
+ import { getLatestRoomDescription } from "./roomHistory";
12
12
  import { saveDataManager } from "./saveDataManager/exports";
13
13
 
14
14
  interface PersistentEntityDescription {
@@ -64,18 +64,19 @@ function postEntityRemove(entity: Entity) {
64
64
  const index = tuple[0];
65
65
 
66
66
  // The persistent entity is despawning, presumably because the player is in the process of leaving
67
- // the room. Keep track of the position for later. We use the previous room list index because at
68
- // this point, the PostNewRoom callback has already fired and we are in a new room.
69
- const level = game.GetLevel();
70
- const previousRoomGridIndex = level.GetPreviousRoomIndex();
71
- const previousRoomListIndex = getRoomListIndex(previousRoomGridIndex);
72
- v.level.persistentEntities.set(index, {
67
+ // the room. Keep track of the position for later. We use the previous room list index because
68
+ // even though the `POST_NEW_ROOM` callback was not fired yet, we have already traveled to the
69
+ // next room.
70
+ const previousRoomDescription = getLatestRoomDescription();
71
+ const previousRoomListIndex = previousRoomDescription.roomListIndex;
72
+ const persistentEntityDescription: PersistentEntityDescription = {
73
73
  entityType: entity.Type,
74
74
  variant: entity.Variant,
75
75
  subType: entity.SubType,
76
76
  roomListIndex: previousRoomListIndex,
77
77
  position: entity.Position,
78
- });
78
+ };
79
+ v.level.persistentEntities.set(index, persistentEntityDescription);
79
80
  }
80
81
 
81
82
  // ModCallbackCustom.POST_NEW_ROOM_REORDERED