isaacscript-common 6.11.1 → 6.11.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.
Files changed (90) hide show
  1. package/dist/callbacks/postFlip.lua +2 -2
  2. package/dist/callbacks/subscriptions/postFirstFlip.d.ts +1 -1
  3. package/dist/callbacks/subscriptions/postFirstFlip.d.ts.map +1 -1
  4. package/dist/callbacks/subscriptions/postFirstFlip.lua +2 -2
  5. package/dist/callbacks/subscriptions/postFlip.d.ts +1 -1
  6. package/dist/callbacks/subscriptions/postFlip.d.ts.map +1 -1
  7. package/dist/callbacks/subscriptions/postFlip.lua +2 -2
  8. package/dist/enums/ModCallbackCustom.d.ts +2 -2
  9. package/dist/enums/private/StageTravelState.d.ts +6 -1
  10. package/dist/enums/private/StageTravelState.d.ts.map +1 -1
  11. package/dist/enums/private/StageTravelState.lua +10 -0
  12. package/dist/enums/private/TrapdoorAnimation.d.ts +6 -0
  13. package/dist/enums/private/TrapdoorAnimation.d.ts.map +1 -0
  14. package/dist/enums/private/TrapdoorAnimation.lua +6 -0
  15. package/dist/features/customGridEntity.d.ts +8 -5
  16. package/dist/features/customGridEntity.d.ts.map +1 -1
  17. package/dist/features/customGridEntity.lua +58 -15
  18. package/dist/features/customStage/exports.d.ts.map +1 -1
  19. package/dist/features/customStage/exports.lua +0 -13
  20. package/dist/features/customStage/init.d.ts.map +1 -1
  21. package/dist/features/customStage/init.lua +16 -1
  22. package/dist/features/customStage/streakText.d.ts.map +1 -1
  23. package/dist/features/customStage/streakText.lua +0 -1
  24. package/dist/features/customTrapdoor/blackSprite.d.ts +2 -0
  25. package/dist/features/customTrapdoor/blackSprite.d.ts.map +1 -0
  26. package/dist/features/customTrapdoor/blackSprite.lua +19 -0
  27. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts +8 -3
  28. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts.map +1 -1
  29. package/dist/features/customTrapdoor/customTrapdoorConstants.lua +9 -1
  30. package/dist/features/customTrapdoor/exports.d.ts +11 -19
  31. package/dist/features/customTrapdoor/exports.d.ts.map +1 -1
  32. package/dist/features/customTrapdoor/exports.lua +48 -82
  33. package/dist/features/customTrapdoor/init.d.ts +3 -0
  34. package/dist/features/customTrapdoor/init.d.ts.map +1 -0
  35. package/dist/features/customTrapdoor/init.lua +173 -0
  36. package/dist/features/customTrapdoor/openClose.d.ts +5 -0
  37. package/dist/features/customTrapdoor/openClose.d.ts.map +1 -0
  38. package/dist/features/customTrapdoor/openClose.lua +60 -0
  39. package/dist/features/customTrapdoor/touched.d.ts +4 -0
  40. package/dist/features/customTrapdoor/touched.d.ts.map +1 -0
  41. package/dist/features/customTrapdoor/touched.lua +141 -0
  42. package/dist/features/customTrapdoor/v.d.ts +16 -2
  43. package/dist/features/customTrapdoor/v.d.ts.map +1 -1
  44. package/dist/features/customTrapdoor/v.lua +8 -6
  45. package/dist/features/extraConsoleCommands/init.d.ts.map +1 -1
  46. package/dist/features/extraConsoleCommands/init.lua +3 -1
  47. package/dist/features/taintedLazarusPlayers.d.ts.map +1 -1
  48. package/dist/features/taintedLazarusPlayers.lua +13 -21
  49. package/dist/functions/log.lua +3 -3
  50. package/dist/functions/playerIndex.d.ts +5 -0
  51. package/dist/functions/playerIndex.d.ts.map +1 -1
  52. package/dist/functions/playerIndex.lua +16 -6
  53. package/dist/functions/table.d.ts +1 -1
  54. package/dist/functions/table.d.ts.map +1 -1
  55. package/dist/initFeatures.d.ts.map +1 -1
  56. package/dist/initFeatures.lua +3 -0
  57. package/dist/interfaces/CustomGridEntityData.d.ts +5 -1
  58. package/dist/interfaces/CustomGridEntityData.d.ts.map +1 -1
  59. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts +3 -0
  60. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts.map +1 -1
  61. package/package.json +2 -2
  62. package/src/callbacks/postFlip.ts +3 -2
  63. package/src/callbacks/subscriptions/postFirstFlip.ts +6 -3
  64. package/src/callbacks/subscriptions/postFlip.ts +6 -3
  65. package/src/enums/ModCallbackCustom.ts +2 -2
  66. package/src/enums/private/StageTravelState.ts +5 -1
  67. package/src/enums/private/TrapdoorAnimation.ts +5 -0
  68. package/src/features/customGridEntity.ts +68 -10
  69. package/src/features/customStage/exports.ts +3 -22
  70. package/src/features/customStage/init.ts +20 -0
  71. package/src/features/customStage/streakText.ts +0 -1
  72. package/src/features/customTrapdoor/blackSprite.ts +16 -0
  73. package/src/features/customTrapdoor/customTrapdoorConstants.ts +13 -3
  74. package/src/features/customTrapdoor/exports.ts +52 -121
  75. package/src/features/customTrapdoor/init.ts +215 -0
  76. package/src/features/customTrapdoor/openClose.ts +103 -0
  77. package/src/features/customTrapdoor/touched.ts +175 -0
  78. package/src/features/customTrapdoor/v.ts +16 -10
  79. package/src/features/deployJSONRoom.ts +1 -1
  80. package/src/features/extraConsoleCommands/init.ts +5 -2
  81. package/src/features/saveDataManager/main.ts +1 -1
  82. package/src/features/taintedLazarusPlayers.ts +32 -31
  83. package/src/functions/deepCopy.ts +2 -2
  84. package/src/functions/entities.ts +1 -1
  85. package/src/functions/playerIndex.ts +12 -0
  86. package/src/functions/rng.ts +1 -1
  87. package/src/functions/table.ts +2 -2
  88. package/src/initFeatures.ts +2 -0
  89. package/src/interfaces/CustomGridEntityData.ts +6 -1
  90. package/src/interfaces/private/CustomTrapdoorDescription.ts +4 -0
@@ -1,23 +1,22 @@
1
1
  import {
2
2
  GridCollisionClass,
3
3
  LevelStage,
4
- RoomType,
5
4
  StageType,
6
5
  } from "isaac-typescript-definitions";
7
6
  import { game } from "../../cachedClasses";
7
+ import { TrapdoorAnimation } from "../../enums/private/TrapdoorAnimation";
8
8
  import { errorIfFeaturesNotInitialized } from "../../featuresInitialized";
9
9
  import { getNextStage, getNextStageType } from "../../functions/nextStage";
10
- import { anyPlayerCloserThan } from "../../functions/positionVelocity";
10
+ import { getRoomListIndex } from "../../functions/roomData";
11
+ import { isVector } from "../../functions/vector";
12
+ import { CustomTrapdoorDescription } from "../../interfaces/private/CustomTrapdoorDescription";
11
13
  import { spawnCustomGridEntity } from "../customGridEntity";
12
- import { getRoomClearGameFrame } from "../roomClearFrame";
13
14
  import {
14
15
  CUSTOM_TRAPDOOR_FEATURE_NAME,
15
16
  GridEntityTypeCustom,
16
- TRAPDOOR_BOSS_REACTION_FRAMES,
17
- TRAPDOOR_OPEN_DISTANCE,
18
- TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS,
19
17
  } from "./customTrapdoorConstants";
20
- import { getCustomTrapdoorDescription } from "./v";
18
+ import { shouldTrapdoorSpawnOpen } from "./openClose";
19
+ import v from "./v";
21
20
 
22
21
  /**
23
22
  * Helper function to spawn a trapdoor grid entity that will have one or more of the following
@@ -26,7 +25,7 @@ import { getCustomTrapdoorDescription } from "./v";
26
25
  * - custom destination (or custom logic for after the player enters)
27
26
  * - custom graphics
28
27
  * - custom logic for opening/closing
29
- * - TODO: animation
28
+ * - TODO: player jumping animation?
30
29
  *
31
30
  * You can use this function to take the player to your custom stage.
32
31
  *
@@ -34,135 +33,67 @@ import { getCustomTrapdoorDescription } from "./v";
34
33
  * respawned every time the player re-enters the room.
35
34
  *
36
35
  * @param gridIndexOrPosition The location in the room to spawn the trapdoor.
37
- * @param _destination Used to specify where the player will go after jumping into the trapdoor. Can
38
- * either be a tuple containing the stage and stage type, a string containing
39
- * the name of a custom stage, or undefined. If undefined, nothing will happen
40
- * after the player jumps in the trapdoor. (Use undefined to perform some custom
41
- * behavior and/or handle the traveling part yourself.) You can also specify a
42
- * function that returns one of these things. By default, the destination will
43
- * be set to the next floor like that of a vanilla trapdoor.
36
+ * @param destination Optional. Used to specify where the player will go after jumping into the
37
+ * trapdoor. Can either be a tuple containing the stage and stage type, or a
38
+ * string containing the name of a custom stage. If not specified at all, then
39
+ * the "normal" destination corresponding to the current stage and room will be
40
+ * used (e.g. the next floor).
44
41
  * @param anm2Path Optional. The path to the anm2 file to use. By default, the vanilla trapdoor anm2
45
- * of "gfx/grid/door_11_trapdoor.anm2" will be used.
46
- * @param _shouldOpenFunc Optional. If the trapdoor is currently closed, this function will run on
47
- * every frame to determine if it should open. By default, a function that
48
- * emulates a vanilla trapdoor will be used.
49
- * @param _shouldCloseFunc Optional. If the trapdoor is currently open, this function will run on
50
- * every frame to determine if it should close. By default, a function that
51
- * emulates a vanilla trapdoor will be used.
52
- * @param _spawnOpen Optional. Whether or not to spawn the trapdoor in an open state. Can either be
53
- * a boolean or a function returning a boolean. By default, a function that
54
- * emulates a vanilla trapdoor will be used.
42
+ * of "gfx/grid/door_11_trapdoor.anm2" will be used. The specified anm2 file must
43
+ * have animations called "Opened", "Closed", and "Open Animation".
44
+ * @param spawnOpen Optional. Whether or not to spawn the trapdoor in an open state. By default,
45
+ * behavior will be used that emulates a vanilla trapdoor.
55
46
  */
56
47
  export function spawnCustomTrapdoor(
57
48
  gridIndexOrPosition: int | Vector,
58
- _destination:
59
- | [stage: LevelStage, stageType: StageType]
60
- | string
61
- | ((
62
- gridEntity: GridEntity,
63
- ) =>
64
- | [stage: LevelStage, stageType: StageType]
65
- | string
66
- | undefined) = defaultDestinationFunc,
49
+ destination?: [stage: LevelStage, stageType: StageType] | string,
67
50
  anm2Path = "gfx/grid/door_11_trapdoor.anm2",
68
- _shouldOpenFunc: (gridEntity: GridEntity) => boolean = defaultShouldOpenFunc,
69
- _shouldCloseFunc: (
70
- gridEntity: GridEntity,
71
- ) => boolean = defaultShouldCloseFunc,
72
- _spawnOpen:
73
- | boolean
74
- | ((gridEntity: GridEntity) => boolean) = defaultShouldSpawnOpenFunc,
51
+ spawnOpen?: boolean,
75
52
  ): GridEntity {
76
53
  errorIfFeaturesNotInitialized(CUSTOM_TRAPDOOR_FEATURE_NAME);
77
54
 
78
- // TODO
79
- return spawnCustomGridEntity(
55
+ const room = game.GetRoom();
56
+ const roomFrameCount = room.GetFrameCount();
57
+ const roomListIndex = getRoomListIndex();
58
+ const gridIndex = isVector(gridIndexOrPosition)
59
+ ? room.GetGridIndex(gridIndexOrPosition)
60
+ : gridIndexOrPosition;
61
+
62
+ const gridEntity = spawnCustomGridEntity(
80
63
  GridEntityTypeCustom.TRAPDOOR_CUSTOM,
81
64
  gridIndexOrPosition,
82
- anm2Path,
83
- "Closed",
84
65
  GridCollisionClass.NONE,
66
+ anm2Path,
67
+ TrapdoorAnimation.OPENED,
85
68
  );
86
- }
87
-
88
- function defaultDestinationFunc(): [stage: LevelStage, stageType: StageType] {
89
- const nextStage = getNextStage();
90
- const nextStageType = getNextStageType();
91
-
92
- return [nextStage, nextStageType];
93
- }
94
-
95
- function defaultShouldOpenFunc(gridEntity: GridEntity): boolean {
96
- const trapdoorDescription = getCustomTrapdoorDescription(gridEntity);
97
- if (trapdoorDescription === undefined) {
98
- return false;
99
- }
100
-
101
- const room = game.GetRoom();
102
- const roomClear = room.IsClear();
103
-
104
- return (
105
- !anyPlayerCloserThan(gridEntity.Position, TRAPDOOR_OPEN_DISTANCE) &&
106
- !isPlayerCloseAfterBoss(gridEntity.Position) &&
107
- !shouldBeClosedFromStartingInRoomWithEnemies(
108
- trapdoorDescription.firstSpawn,
109
- roomClear,
110
- )
111
- );
112
- }
113
69
 
114
- function isPlayerCloseAfterBoss(position: Vector) {
115
- const gameFrameCount = game.GetFrameCount();
116
- const room = game.GetRoom();
117
- const roomType = room.GetType();
118
- const roomClearGameFrame = getRoomClearGameFrame();
119
-
120
- // In order to prevent a player from accidentally entering a freshly-spawned trapdoor after
121
- // killing the boss of the floor, we use a wider open distance for a short amount of frames.
122
- if (
123
- roomType !== RoomType.BOSS ||
124
- roomClearGameFrame === undefined ||
125
- gameFrameCount >= roomClearGameFrame + TRAPDOOR_BOSS_REACTION_FRAMES
126
- ) {
127
- return false;
70
+ const firstSpawn = roomFrameCount !== 0;
71
+ const open =
72
+ spawnOpen === undefined
73
+ ? shouldTrapdoorSpawnOpen(gridEntity, firstSpawn)
74
+ : spawnOpen;
75
+ const destinationToUse =
76
+ destination === undefined ? getDefaultDestination() : destination;
77
+
78
+ const roomTrapdoorMap = v.level.trapdoors.getAndSetDefault(roomListIndex);
79
+ const customTrapdoorDescription: CustomTrapdoorDescription = {
80
+ open,
81
+ destination: destinationToUse,
82
+ firstSpawn,
83
+ };
84
+ roomTrapdoorMap.set(gridIndex, customTrapdoorDescription);
85
+
86
+ if (!open) {
87
+ const sprite = gridEntity.GetSprite();
88
+ sprite.Play(TrapdoorAnimation.CLOSED, true);
128
89
  }
129
90
 
130
- return anyPlayerCloserThan(position, TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS);
91
+ return gridEntity;
131
92
  }
132
93
 
133
- function shouldBeClosedFromStartingInRoomWithEnemies(
134
- firstSpawn: boolean,
135
- roomClear: boolean,
136
- ) {
137
- return firstSpawn && !roomClear;
138
- }
139
-
140
- /** By default, trapdoors will never close if they are already open. */
141
- function defaultShouldCloseFunc(): boolean {
142
- return false;
143
- }
144
-
145
- function defaultShouldSpawnOpenFunc(gridEntity: GridEntity): boolean {
146
- const room = game.GetRoom();
147
- const roomFrameCount = room.GetFrameCount();
148
- const roomClear = room.IsClear();
149
-
150
- // Trapdoors created after a room has already initialized should spawn closed by default:
151
- // - Trapdoors created after bosses should spawn closed so that players do not accidentally jump
152
- // into them.
153
- // - Trapdoors created by We Need to Go Deeper should spawn closed because the player will be
154
- // standing on top of them.
155
- if (roomFrameCount > 0) {
156
- return false;
157
- }
158
-
159
- // If we just entered a new room with enemies in it, spawn the trapdoor closed so that the player
160
- // has to defeat the enemies first before using the trapdoor.
161
- if (!roomClear) {
162
- return false;
163
- }
94
+ function getDefaultDestination(): [stage: LevelStage, stageType: StageType] {
95
+ const nextStage = getNextStage();
96
+ const nextStageType = getNextStageType();
164
97
 
165
- // If we just entered a new room that is already cleared, spawn the trapdoor closed if we are
166
- // standing close to it, and open otherwise.
167
- return defaultShouldOpenFunc(gridEntity);
98
+ return [nextStage, nextStageType];
168
99
  }
@@ -0,0 +1,215 @@
1
+ import {
2
+ Direction,
3
+ ModCallback,
4
+ RoomTransitionAnim,
5
+ } from "isaac-typescript-definitions";
6
+ import { game } from "../../cachedClasses";
7
+ import { ModUpgraded } from "../../classes/ModUpgraded";
8
+ import { ModCallbackCustom } from "../../enums/ModCallbackCustom";
9
+ import { StageTravelState } from "../../enums/private/StageTravelState";
10
+ import { movePlayersToCenter } from "../../functions/playerCenter";
11
+ import { getAllPlayers } from "../../functions/playerIndex";
12
+ import { getRoomGridIndex, getRoomListIndex } from "../../functions/roomData";
13
+ import { setStage } from "../../functions/stage";
14
+ import { isString } from "../../functions/types";
15
+ import { setCustomStage } from "../customStage/exports";
16
+ import { topStreakTextStart } from "../customStage/streakText";
17
+ import { enableAllInputs } from "../disableInputs";
18
+ import { runNextGameFrame } from "../runInNFrames";
19
+ import { runNextRoom } from "../runNextRoom";
20
+ import { saveDataManager } from "../saveDataManager/exports";
21
+ import { drawBlackSprite } from "./blackSprite";
22
+ import {
23
+ CUSTOM_TRAPDOOR_FEATURE_NAME,
24
+ PIXELATION_TO_BLACK_FRAMES,
25
+ } from "./customTrapdoorConstants";
26
+ import { checkCustomTrapdoorOpenClose } from "./openClose";
27
+ import { checkCustomTrapdoorPlayerTouched } from "./touched";
28
+ import v from "./v";
29
+
30
+ export function customTrapdoorInit(mod: ModUpgraded): void {
31
+ saveDataManager(CUSTOM_TRAPDOOR_FEATURE_NAME, v);
32
+
33
+ mod.AddCallback(ModCallback.POST_RENDER, postRender); // 2
34
+ mod.AddCallback(ModCallback.POST_PEFFECT_UPDATE, postPEffectUpdate); // 4
35
+ mod.AddCallbackCustom(
36
+ ModCallbackCustom.POST_GRID_ENTITY_UPDATE,
37
+ postGridEntityUpdateTrapdoor,
38
+ );
39
+ }
40
+
41
+ // ModCallback.POST_RENDER (2)
42
+ function postRender() {
43
+ checkAllPlayersJumpComplete();
44
+ checkPixelationToBlackComplete();
45
+ checkPausingOnBlackComplete();
46
+ checkAllPlayersLayingDownComplete();
47
+ drawBlackSprite();
48
+ }
49
+
50
+ function checkAllPlayersJumpComplete() {
51
+ if (v.run.state !== StageTravelState.PLAYERS_JUMPING_DOWN) {
52
+ return;
53
+ }
54
+
55
+ if (anyPlayerPlayingExtraAnimation()) {
56
+ return;
57
+ }
58
+
59
+ const renderFrameCount = Isaac.GetFrameCount();
60
+ const roomGridIndex = getRoomGridIndex();
61
+
62
+ v.run.state = StageTravelState.PIXELATION_TO_BLACK;
63
+ v.run.stateRenderFrame = renderFrameCount;
64
+
65
+ // In order to display the pixelation effect that should happen when we go to a new floor, we need
66
+ // to start a room transition. We arbitrarily pick the current room for this purpose.
67
+ game.StartRoomTransition(
68
+ roomGridIndex,
69
+ Direction.NO_DIRECTION,
70
+ RoomTransitionAnim.PIXELATION,
71
+ );
72
+ }
73
+
74
+ function checkPixelationToBlackComplete() {
75
+ if (
76
+ v.run.state !== StageTravelState.PIXELATION_TO_BLACK ||
77
+ v.run.stateRenderFrame === null
78
+ ) {
79
+ return;
80
+ }
81
+
82
+ const hud = game.GetHUD();
83
+ const renderFrameCount = Isaac.GetFrameCount();
84
+ const roomGridIndex = getRoomGridIndex();
85
+
86
+ const renderFrameScreenBlack =
87
+ v.run.stateRenderFrame + PIXELATION_TO_BLACK_FRAMES;
88
+ if (renderFrameCount < renderFrameScreenBlack) {
89
+ return;
90
+ }
91
+
92
+ v.run.state = StageTravelState.PAUSING_ON_BLACK;
93
+ v.run.stateRenderFrame = renderFrameCount;
94
+
95
+ hud.SetVisible(false);
96
+ goToCustomDestination();
97
+
98
+ // Start another pixelation effect. This time, we will keep the screen black with the sprite, and
99
+ // then remove the black sprite once the pixelation effect is halfway complete.
100
+ game.StartRoomTransition(
101
+ roomGridIndex,
102
+ Direction.NO_DIRECTION,
103
+ RoomTransitionAnim.PIXELATION,
104
+ );
105
+ }
106
+
107
+ function checkPausingOnBlackComplete() {
108
+ if (
109
+ v.run.state !== StageTravelState.PAUSING_ON_BLACK ||
110
+ v.run.stateRenderFrame === null
111
+ ) {
112
+ return;
113
+ }
114
+
115
+ const hud = game.GetHUD();
116
+ const renderFrameCount = Isaac.GetFrameCount();
117
+
118
+ const renderFrameScreenBlack =
119
+ v.run.stateRenderFrame + PIXELATION_TO_BLACK_FRAMES;
120
+ if (renderFrameCount < renderFrameScreenBlack) {
121
+ return;
122
+ }
123
+
124
+ v.run.state = StageTravelState.PIXELATION_TO_ROOM;
125
+
126
+ hud.SetVisible(true);
127
+
128
+ runNextRoom(() => {
129
+ // After the room transition, the players will be placed next to a door, but they should be in
130
+ // the center of the room to emulate what happens on a vanilla stage.
131
+ movePlayersToCenter();
132
+
133
+ v.run.state = StageTravelState.PLAYERS_LAYING_DOWN;
134
+
135
+ for (const player of getAllPlayers()) {
136
+ player.AnimateAppear();
137
+ }
138
+ });
139
+
140
+ // In vanilla, the streak text appears about when the pixelation has faded and while Isaac is
141
+ // still laying on the ground. Unfortunately, we cannot exactly replicate the vanilla timing,
142
+ // because the level text will bug out and smear the background if we play it from a `POST_RENDER`
143
+ // callback. Thus, we run it on the next game frame as a workaround.
144
+ runNextGameFrame(() => {
145
+ topStreakTextStart();
146
+ });
147
+ }
148
+
149
+ function checkAllPlayersLayingDownComplete() {
150
+ if (v.run.state !== StageTravelState.PLAYERS_LAYING_DOWN) {
151
+ return;
152
+ }
153
+
154
+ if (anyPlayerPlayingExtraAnimation()) {
155
+ return;
156
+ }
157
+
158
+ v.run.state = StageTravelState.NONE;
159
+
160
+ enableAllInputs(CUSTOM_TRAPDOOR_FEATURE_NAME);
161
+ }
162
+
163
+ function goToCustomDestination() {
164
+ if (v.run.destination === null) {
165
+ return;
166
+ }
167
+
168
+ if (isString(v.run.destination)) {
169
+ setCustomStage("Slaughterhouse");
170
+ } else {
171
+ const [stage, stageType] = v.run.destination;
172
+ setStage(stage, stageType);
173
+ }
174
+ }
175
+
176
+ function anyPlayerPlayingExtraAnimation() {
177
+ const players = getAllPlayers();
178
+ return players.some((player) => !player.IsExtraAnimationFinished());
179
+ }
180
+
181
+ // ModCallback.POST_PEFFECT_UPDATE (4)
182
+ function postPEffectUpdate(player: EntityPlayer) {
183
+ checkJumpComplete(player);
184
+ }
185
+
186
+ function checkJumpComplete(player: EntityPlayer) {
187
+ if (v.run.state !== StageTravelState.PLAYERS_JUMPING_DOWN) {
188
+ return;
189
+ }
190
+
191
+ // In this state, the players are jumping down the hole (i.e. playing the "Trapdoor" animation).
192
+ // When it completes, they will return to normal (i.e. just standing on top of the trapdoor).
193
+ // Thus, make them invisible at the end of the animation. (They will automatically be set to
194
+ // visible again once we travel to the next floor.)
195
+ const sprite = player.GetSprite();
196
+ if (sprite.IsFinished("Trapdoor")) {
197
+ player.Visible = false;
198
+ }
199
+ }
200
+
201
+ // ModCallbackCustom.POST_GRID_ENTITY_UPDATE
202
+ // GridEntityType.TRAPDOOR (17)
203
+ function postGridEntityUpdateTrapdoor(gridEntity: GridEntity) {
204
+ const roomListIndex = getRoomListIndex();
205
+ const gridIndex = gridEntity.GetGridIndex();
206
+
207
+ const roomTrapdoorMap = v.level.trapdoors.getAndSetDefault(roomListIndex);
208
+ const trapdoorDescription = roomTrapdoorMap.get(gridIndex);
209
+ if (trapdoorDescription === undefined) {
210
+ return;
211
+ }
212
+
213
+ checkCustomTrapdoorOpenClose(gridEntity, trapdoorDescription);
214
+ checkCustomTrapdoorPlayerTouched(gridEntity, trapdoorDescription);
215
+ }
@@ -0,0 +1,103 @@
1
+ import { RoomType } from "isaac-typescript-definitions";
2
+ import { game } from "../../cachedClasses";
3
+ import { TrapdoorAnimation } from "../../enums/private/TrapdoorAnimation";
4
+ import { anyPlayerCloserThan } from "../../functions/positionVelocity";
5
+ import { CustomTrapdoorDescription } from "../../interfaces/private/CustomTrapdoorDescription";
6
+ import { getRoomClearGameFrame } from "../roomClearFrame";
7
+ import {
8
+ TRAPDOOR_BOSS_REACTION_FRAMES,
9
+ TRAPDOOR_OPEN_DISTANCE,
10
+ TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS,
11
+ } from "./customTrapdoorConstants";
12
+
13
+ export function checkCustomTrapdoorOpenClose(
14
+ gridEntity: GridEntity,
15
+ trapdoorDescription: CustomTrapdoorDescription,
16
+ ): void {
17
+ /** By default, trapdoors will never close if they are already open. */
18
+ if (trapdoorDescription.open) {
19
+ return;
20
+ }
21
+
22
+ if (shouldTrapdoorOpen(gridEntity, trapdoorDescription.firstSpawn)) {
23
+ open(gridEntity, trapdoorDescription);
24
+ }
25
+ }
26
+
27
+ function shouldTrapdoorOpen(
28
+ gridEntity: GridEntity,
29
+ firstSpawn: boolean,
30
+ ): boolean {
31
+ const room = game.GetRoom();
32
+ const roomClear = room.IsClear();
33
+
34
+ return (
35
+ !anyPlayerCloserThan(gridEntity.Position, TRAPDOOR_OPEN_DISTANCE) &&
36
+ !isPlayerCloseAfterBoss(gridEntity.Position) &&
37
+ !shouldBeClosedFromStartingInRoomWithEnemies(firstSpawn, roomClear)
38
+ );
39
+ }
40
+
41
+ function isPlayerCloseAfterBoss(position: Vector) {
42
+ const gameFrameCount = game.GetFrameCount();
43
+ const room = game.GetRoom();
44
+ const roomType = room.GetType();
45
+ const roomClearGameFrame = getRoomClearGameFrame();
46
+
47
+ // In order to prevent a player from accidentally entering a freshly-spawned trapdoor after
48
+ // killing the boss of the floor, we use a wider open distance for a short amount of frames.
49
+ if (
50
+ roomType !== RoomType.BOSS ||
51
+ roomClearGameFrame === undefined ||
52
+ gameFrameCount >= roomClearGameFrame + TRAPDOOR_BOSS_REACTION_FRAMES
53
+ ) {
54
+ return false;
55
+ }
56
+
57
+ return anyPlayerCloserThan(position, TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS);
58
+ }
59
+
60
+ function shouldBeClosedFromStartingInRoomWithEnemies(
61
+ firstSpawn: boolean,
62
+ roomClear: boolean,
63
+ ) {
64
+ return firstSpawn && !roomClear;
65
+ }
66
+
67
+ function open(
68
+ gridEntity: GridEntity,
69
+ trapdoorDescription: CustomTrapdoorDescription,
70
+ ) {
71
+ trapdoorDescription.open = true;
72
+
73
+ const sprite = gridEntity.GetSprite();
74
+ sprite.Play(TrapdoorAnimation.OPEN_ANIMATION, true);
75
+ }
76
+
77
+ export function shouldTrapdoorSpawnOpen(
78
+ gridEntity: GridEntity,
79
+ firstSpawn: boolean,
80
+ ): boolean {
81
+ const room = game.GetRoom();
82
+ const roomFrameCount = room.GetFrameCount();
83
+ const roomClear = room.IsClear();
84
+
85
+ // Trapdoors created after a room has already initialized should spawn closed by default:
86
+ // - Trapdoors created after bosses should spawn closed so that players do not accidentally jump
87
+ // into them.
88
+ // - Trapdoors created by We Need to Go Deeper should spawn closed because the player will be
89
+ // standing on top of them.
90
+ if (roomFrameCount > 0) {
91
+ return false;
92
+ }
93
+
94
+ // If we just entered a new room with enemies in it, spawn the trapdoor closed so that the player
95
+ // has to defeat the enemies first before using the trapdoor.
96
+ if (!roomClear) {
97
+ return false;
98
+ }
99
+
100
+ // If we just entered a new room that is already cleared, spawn the trapdoor closed if we are
101
+ // standing close to it, and open otherwise.
102
+ return shouldTrapdoorOpen(gridEntity, firstSpawn);
103
+ }