isaacscript-common 6.10.2 → 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 (169) hide show
  1. package/dist/callbacks/postFlip.lua +2 -2
  2. package/dist/callbacks/postPlayerFatalDamage.lua +1 -1
  3. package/dist/callbacks/postPlayerInitFirst.d.ts +2 -0
  4. package/dist/callbacks/postPlayerInitFirst.d.ts.map +1 -0
  5. package/dist/callbacks/postPlayerInitFirst.lua +42 -0
  6. package/dist/callbacks/postPlayerInitLate.lua +5 -5
  7. package/dist/callbacks/postPlayerReorderedCallbacks.d.ts +2 -0
  8. package/dist/callbacks/postPlayerReorderedCallbacks.d.ts.map +1 -0
  9. package/dist/callbacks/{postPlayerReordered.lua → postPlayerReorderedCallbacks.lua} +11 -37
  10. package/dist/callbacks/subscriptions/postFirstFlip.d.ts +1 -1
  11. package/dist/callbacks/subscriptions/postFirstFlip.d.ts.map +1 -1
  12. package/dist/callbacks/subscriptions/postFirstFlip.lua +2 -2
  13. package/dist/callbacks/subscriptions/postFlip.d.ts +1 -1
  14. package/dist/callbacks/subscriptions/postFlip.d.ts.map +1 -1
  15. package/dist/callbacks/subscriptions/postFlip.lua +2 -2
  16. package/dist/callbacks/subscriptions/{postPlayerInitReordered.d.ts → postPlayerInitFirst.d.ts} +2 -2
  17. package/dist/callbacks/subscriptions/postPlayerInitFirst.d.ts.map +1 -0
  18. package/dist/callbacks/subscriptions/{postPlayerInitReordered.lua → postPlayerInitFirst.lua} +3 -3
  19. package/dist/enums/ModCallbackCustom.d.ts +19 -17
  20. package/dist/enums/ModCallbackCustom.d.ts.map +1 -1
  21. package/dist/enums/ModCallbackCustom.lua +3 -3
  22. package/dist/enums/private/StageTravelState.d.ts +9 -0
  23. package/dist/enums/private/StageTravelState.d.ts.map +1 -0
  24. package/dist/enums/private/StageTravelState.lua +15 -0
  25. package/dist/enums/private/TrapdoorAnimation.d.ts +6 -0
  26. package/dist/enums/private/TrapdoorAnimation.d.ts.map +1 -0
  27. package/dist/enums/private/TrapdoorAnimation.lua +6 -0
  28. package/dist/features/customGridEntity.d.ts +8 -5
  29. package/dist/features/customGridEntity.d.ts.map +1 -1
  30. package/dist/features/customGridEntity.lua +66 -17
  31. package/dist/features/customStage/exports.d.ts.map +1 -1
  32. package/dist/features/customStage/exports.lua +0 -13
  33. package/dist/features/customStage/init.d.ts.map +1 -1
  34. package/dist/features/customStage/init.lua +24 -2
  35. package/dist/features/customStage/streakText.d.ts +6 -0
  36. package/dist/features/customStage/streakText.d.ts.map +1 -1
  37. package/dist/features/customStage/streakText.lua +16 -12
  38. package/dist/features/customStage/versusScreen.d.ts +6 -0
  39. package/dist/features/customStage/versusScreen.d.ts.map +1 -1
  40. package/dist/features/customStage/versusScreen.lua +10 -5
  41. package/dist/features/customTrapdoor/blackSprite.d.ts +2 -0
  42. package/dist/features/customTrapdoor/blackSprite.d.ts.map +1 -0
  43. package/dist/features/customTrapdoor/blackSprite.lua +19 -0
  44. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts +15 -0
  45. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts.map +1 -0
  46. package/dist/features/customTrapdoor/customTrapdoorConstants.lua +16 -0
  47. package/dist/features/customTrapdoor/exports.d.ts +29 -0
  48. package/dist/features/customTrapdoor/exports.d.ts.map +1 -0
  49. package/dist/features/customTrapdoor/exports.lua +93 -0
  50. package/dist/features/customTrapdoor/init.d.ts +3 -0
  51. package/dist/features/customTrapdoor/init.d.ts.map +1 -0
  52. package/dist/features/customTrapdoor/init.lua +173 -0
  53. package/dist/features/customTrapdoor/openClose.d.ts +5 -0
  54. package/dist/features/customTrapdoor/openClose.d.ts.map +1 -0
  55. package/dist/features/customTrapdoor/openClose.lua +60 -0
  56. package/dist/features/customTrapdoor/touched.d.ts +4 -0
  57. package/dist/features/customTrapdoor/touched.d.ts.map +1 -0
  58. package/dist/features/customTrapdoor/touched.lua +141 -0
  59. package/dist/features/customTrapdoor/v.d.ts +18 -0
  60. package/dist/features/customTrapdoor/v.d.ts.map +1 -0
  61. package/dist/features/customTrapdoor/v.lua +17 -0
  62. package/dist/features/deployJSONRoom.d.ts.map +1 -1
  63. package/dist/features/deployJSONRoom.lua +1 -1
  64. package/dist/features/extraConsoleCommands/init.d.ts.map +1 -1
  65. package/dist/features/extraConsoleCommands/init.lua +3 -1
  66. package/dist/features/extraConsoleCommands/listCommands.lua +2 -2
  67. package/dist/features/taintedLazarusPlayers.d.ts.map +1 -1
  68. package/dist/features/taintedLazarusPlayers.lua +13 -21
  69. package/dist/functions/{character.d.ts → characters.d.ts} +3 -1
  70. package/dist/functions/characters.d.ts.map +1 -0
  71. package/dist/functions/{character.lua → characters.lua} +12 -0
  72. package/dist/functions/deepCopyTests.lua +35 -45
  73. package/dist/functions/jsonHelpers.d.ts +6 -0
  74. package/dist/functions/jsonHelpers.d.ts.map +1 -1
  75. package/dist/functions/jsonHelpers.lua +9 -3
  76. package/dist/functions/log.lua +3 -3
  77. package/dist/functions/playerIndex.d.ts +11 -2
  78. package/dist/functions/playerIndex.d.ts.map +1 -1
  79. package/dist/functions/playerIndex.lua +20 -8
  80. package/dist/functions/players.lua +4 -4
  81. package/dist/functions/revive.lua +2 -2
  82. package/dist/functions/table.d.ts +1 -1
  83. package/dist/functions/table.d.ts.map +1 -1
  84. package/dist/index.d.ts +3 -2
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.lua +10 -2
  87. package/dist/initCustomCallbacks.d.ts.map +1 -1
  88. package/dist/initCustomCallbacks.lua +5 -2
  89. package/dist/initFeatures.d.ts +1 -2
  90. package/dist/initFeatures.d.ts.map +1 -1
  91. package/dist/initFeatures.lua +10 -2
  92. package/dist/interfaces/AddCallbackParameterCustom.d.ts +2 -2
  93. package/dist/interfaces/AddCallbackParameterCustom.d.ts.map +1 -1
  94. package/dist/interfaces/CustomGridEntityData.d.ts +6 -2
  95. package/dist/interfaces/CustomGridEntityData.d.ts.map +1 -1
  96. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts +7 -0
  97. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts.map +1 -0
  98. package/dist/interfaces/private/CustomTrapdoorDescription.lua +2 -0
  99. package/dist/lib/jsonLua.lua +390 -0
  100. package/dist/objects/callbackRegisterFunctions.d.ts.map +1 -1
  101. package/dist/objects/callbackRegisterFunctions.lua +3 -3
  102. package/dist/objects/characterDamageMultipliers.d.ts +6 -0
  103. package/dist/objects/characterDamageMultipliers.d.ts.map +1 -0
  104. package/dist/objects/characterDamageMultipliers.lua +49 -0
  105. package/dist/upgradeMod.d.ts.map +1 -1
  106. package/dist/upgradeMod.lua +2 -4
  107. package/package.json +2 -2
  108. package/src/callbacks/customRevive.ts +3 -3
  109. package/src/callbacks/itemPickup.ts +3 -3
  110. package/src/callbacks/postAmbush.ts +3 -3
  111. package/src/callbacks/postEsauJr.ts +3 -3
  112. package/src/callbacks/postFlip.ts +6 -5
  113. package/src/callbacks/postGridEntity.ts +5 -5
  114. package/src/callbacks/postPlayerCollectible.ts +2 -2
  115. package/src/callbacks/postPlayerFatalDamage.ts +5 -0
  116. package/src/callbacks/postPlayerInitFirst.ts +57 -0
  117. package/src/callbacks/postPlayerInitLate.ts +9 -5
  118. package/src/callbacks/{postPlayerReordered.ts → postPlayerReorderedCallbacks.ts} +9 -29
  119. package/src/callbacks/postSlotInitUpdate.ts +5 -2
  120. package/src/callbacks/postSlotRender.ts +2 -2
  121. package/src/callbacks/reorderedCallbacks.ts +1 -1
  122. package/src/callbacks/subscriptions/postFirstFlip.ts +6 -3
  123. package/src/callbacks/subscriptions/postFlip.ts +6 -3
  124. package/src/callbacks/subscriptions/{postPlayerInitReordered.ts → postPlayerInitFirst.ts} +6 -6
  125. package/src/enums/ModCallbackCustom.ts +19 -17
  126. package/src/enums/private/StageTravelState.ts +8 -0
  127. package/src/enums/private/TrapdoorAnimation.ts +5 -0
  128. package/src/features/customGridEntity.ts +93 -12
  129. package/src/features/customStage/exports.ts +3 -22
  130. package/src/features/customStage/init.ts +30 -1
  131. package/src/features/customStage/streakText.ts +13 -5
  132. package/src/features/customStage/versusScreen.ts +20 -12
  133. package/src/features/customTrapdoor/blackSprite.ts +16 -0
  134. package/src/features/customTrapdoor/customTrapdoorConstants.ts +23 -0
  135. package/src/features/customTrapdoor/exports.ts +99 -0
  136. package/src/features/customTrapdoor/init.ts +215 -0
  137. package/src/features/customTrapdoor/openClose.ts +103 -0
  138. package/src/features/customTrapdoor/touched.ts +175 -0
  139. package/src/features/customTrapdoor/v.ts +26 -0
  140. package/src/features/deployJSONRoom.ts +6 -1
  141. package/src/features/extraConsoleCommands/init.ts +5 -2
  142. package/src/features/extraConsoleCommands/listCommands.ts +1 -1
  143. package/src/features/saveDataManager/main.ts +1 -1
  144. package/src/features/taintedLazarusPlayers.ts +32 -31
  145. package/src/functions/{character.ts → characters.ts} +13 -0
  146. package/src/functions/deepCopy.ts +2 -2
  147. package/src/functions/deepCopyTests.ts +44 -48
  148. package/src/functions/entities.ts +1 -1
  149. package/src/functions/jsonHelpers.ts +9 -3
  150. package/src/functions/playerIndex.ts +18 -2
  151. package/src/functions/players.ts +1 -1
  152. package/src/functions/revive.ts +1 -1
  153. package/src/functions/rng.ts +1 -1
  154. package/src/functions/table.ts +2 -2
  155. package/src/index.ts +6 -2
  156. package/src/initCustomCallbacks.ts +3 -1
  157. package/src/initFeatures.ts +9 -2
  158. package/src/interfaces/AddCallbackParameterCustom.ts +2 -2
  159. package/src/interfaces/CustomGridEntityData.ts +7 -2
  160. package/src/interfaces/private/CustomTrapdoorDescription.ts +7 -0
  161. package/src/lib/jsonLua.d.ts +10 -0
  162. package/src/lib/jsonLua.lua +390 -0
  163. package/src/objects/callbackRegisterFunctions.ts +2 -3
  164. package/src/objects/characterDamageMultipliers.ts +49 -0
  165. package/src/upgradeMod.ts +2 -3
  166. package/dist/callbacks/postPlayerReordered.d.ts +0 -2
  167. package/dist/callbacks/postPlayerReordered.d.ts.map +0 -1
  168. package/dist/callbacks/subscriptions/postPlayerInitReordered.d.ts.map +0 -1
  169. package/dist/functions/character.d.ts.map +0 -1
@@ -88,8 +88,6 @@ const TEXT_OUT_SCALES = [
88
88
  * "loadGraphics", but still manage playing its animations in the code below.
89
89
  */
90
90
  const topStreakSprite = Sprite();
91
- topStreakSprite.Load("resources/gfx/ui/ui_streak.anm2", false);
92
- topStreakSprite.PlaybackSpeed = TEXT_PLAYBACK_SPEED;
93
91
 
94
92
  /**
95
93
  * We do not actually need to render this sprite at all. It's only purpose is to be an invisible
@@ -97,8 +95,19 @@ topStreakSprite.PlaybackSpeed = TEXT_PLAYBACK_SPEED;
97
95
  * "loadGraphics", but still manage playing its animations in the code below.
98
96
  */
99
97
  const bottomStreakSprite = Sprite();
100
- bottomStreakSprite.Load("resources/gfx/ui/ui_streak.anm2", false);
101
- bottomStreakSprite.PlaybackSpeed = TEXT_PLAYBACK_SPEED;
98
+
99
+ /**
100
+ * We must load the sprites in an init function to prevent issues with mods that replace the vanilla
101
+ * files. (For some reason, loading the sprites will cause the overwrite to no longer apply on the
102
+ * second and subsequent runs.)
103
+ */
104
+ export function streakTextInit(): void {
105
+ topStreakSprite.Load("resources/gfx/ui/ui_streak.anm2", false);
106
+ topStreakSprite.PlaybackSpeed = TEXT_PLAYBACK_SPEED;
107
+
108
+ bottomStreakSprite.Load("resources/gfx/ui/ui_streak.anm2", false);
109
+ bottomStreakSprite.PlaybackSpeed = TEXT_PLAYBACK_SPEED;
110
+ }
102
111
 
103
112
  // ModCallback.POST_RENDER (2)
104
113
  export function streakTextPostRender(): void {
@@ -278,7 +287,6 @@ function renderSprite(
278
287
  const adjustedX = centeredX + adjustment;
279
288
  const adjustedY = position.Y + STREAK_TEXT_BOTTOM_Y_OFFSET;
280
289
 
281
- sprite.RenderLayer(0, position);
282
290
  font.DrawStringScaled(
283
291
  name,
284
292
  adjustedX,
@@ -66,30 +66,38 @@ const PLAYER_PORTRAIT_PNG_PATH_PREFIX = "gfx/ui/stage";
66
66
  const VANILLA_VERSUS_PLAYBACK_SPEED = 0.5;
67
67
 
68
68
  const versusScreenSprite = Sprite();
69
- versusScreenSprite.Load("gfx/ui/boss/versusscreen.anm2", false);
70
-
71
- // In vanilla, the "overlay.png" file has a white background. We must convert it to a PNG that uses
72
- // a transparent background in order for the background behind it to be visible. We use the same
73
- // "overlay.png" file as StageAPI uses for this purpose.
74
- versusScreenSprite.ReplaceSpritesheet(
75
- OVERLAY_ANM2_LAYER,
76
- `${ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/overlay.png`,
77
- );
78
- versusScreenSprite.LoadGraphics();
79
69
 
80
70
  /**
81
71
  * Unfortunately, we must split the background layer into an entirely different sprite so that we
82
72
  * can color it with the `Color` property.
83
73
  */
84
74
  const versusScreenBackgroundSprite = Sprite();
85
- versusScreenBackgroundSprite.Load("gfx/ui/boss/versusscreen.anm2", true);
86
75
 
87
76
  /**
88
77
  * Unfortunately, we must split the dirt layer into an entirely different sprite so that we can
89
78
  * color it with the `Color` property.
90
79
  */
91
80
  const versusScreenDirtSpotSprite = Sprite();
92
- versusScreenDirtSpotSprite.Load("gfx/ui/boss/versusscreen.anm2", true);
81
+
82
+ /**
83
+ * We must load the sprites in an init function to prevent issues with mods that replace the vanilla
84
+ * files. (For some reason, loading the sprites will cause the overwrite to no longer apply on the
85
+ * second and subsequent runs.)
86
+ */
87
+ export function versusScreenInit(): void {
88
+ // In vanilla, the "overlay.png" file has a white background. We must convert it to a PNG that
89
+ // uses a transparent background in order for the background behind it to be visible. We use the
90
+ // same "overlay.png" file as StageAPI uses for this purpose.
91
+ versusScreenSprite.Load("gfx/ui/boss/versusscreen.anm2", false);
92
+ versusScreenSprite.ReplaceSpritesheet(
93
+ OVERLAY_ANM2_LAYER,
94
+ `${ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/overlay.png`,
95
+ );
96
+ versusScreenSprite.LoadGraphics();
97
+
98
+ versusScreenBackgroundSprite.Load("gfx/ui/boss/versusscreen.anm2", true);
99
+ versusScreenDirtSpotSprite.Load("gfx/ui/boss/versusscreen.anm2", true);
100
+ }
93
101
 
94
102
  export function playVersusScreenAnimation(customStage: CustomStage): void {
95
103
  const room = game.GetRoom();
@@ -0,0 +1,16 @@
1
+ import { VectorZero } from "../../constants";
2
+ import { StageTravelState } from "../../enums/private/StageTravelState";
3
+ import { ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH } from "../customStage/customStageConstants";
4
+ import v from "./v";
5
+
6
+ const sprite = Sprite();
7
+ sprite.Load(`${ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/black.anm2`, true);
8
+ sprite.SetFrame("Default", 0);
9
+
10
+ export function drawBlackSprite(): void {
11
+ if (v.run.state !== StageTravelState.PAUSING_ON_BLACK) {
12
+ return;
13
+ }
14
+
15
+ sprite.RenderLayer(0, VectorZero);
16
+ }
@@ -0,0 +1,23 @@
1
+ import { GridEntityType } from "isaac-typescript-definitions";
2
+
3
+ export const CUSTOM_TRAPDOOR_FEATURE_NAME = "customTrapdoor";
4
+
5
+ export const GridEntityTypeCustom = {
6
+ TRAPDOOR_CUSTOM: 1000 as GridEntityType,
7
+ } as const;
8
+
9
+ /** This also applies to crawl spaces. The value was determined through trial and error. */
10
+ export const TRAPDOOR_OPEN_DISTANCE = 60;
11
+
12
+ export const TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS = TRAPDOOR_OPEN_DISTANCE * 2.5;
13
+ export const TRAPDOOR_BOSS_REACTION_FRAMES = 30;
14
+
15
+ export const TRAPDOOR_TOUCH_DISTANCE = 16.5;
16
+
17
+ export const ANIMATIONS_THAT_PREVENT_STAGE_TRAVEL: ReadonlySet<string> =
18
+ new Set(["Happy", "Sad", "Jump"]);
19
+
20
+ export const PIXELATION_TO_BLACK_FRAMES = 52;
21
+
22
+ export const OTHER_PLAYER_TRAPDOOR_JUMP_DELAY_GAME_FRAMES = 10;
23
+ export const OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES = 8;
@@ -0,0 +1,99 @@
1
+ import {
2
+ GridCollisionClass,
3
+ LevelStage,
4
+ StageType,
5
+ } from "isaac-typescript-definitions";
6
+ import { game } from "../../cachedClasses";
7
+ import { TrapdoorAnimation } from "../../enums/private/TrapdoorAnimation";
8
+ import { errorIfFeaturesNotInitialized } from "../../featuresInitialized";
9
+ import { getNextStage, getNextStageType } from "../../functions/nextStage";
10
+ import { getRoomListIndex } from "../../functions/roomData";
11
+ import { isVector } from "../../functions/vector";
12
+ import { CustomTrapdoorDescription } from "../../interfaces/private/CustomTrapdoorDescription";
13
+ import { spawnCustomGridEntity } from "../customGridEntity";
14
+ import {
15
+ CUSTOM_TRAPDOOR_FEATURE_NAME,
16
+ GridEntityTypeCustom,
17
+ } from "./customTrapdoorConstants";
18
+ import { shouldTrapdoorSpawnOpen } from "./openClose";
19
+ import v from "./v";
20
+
21
+ /**
22
+ * Helper function to spawn a trapdoor grid entity that will have one or more of the following
23
+ * attributes:
24
+ *
25
+ * - custom destination (or custom logic for after the player enters)
26
+ * - custom graphics
27
+ * - custom logic for opening/closing
28
+ * - TODO: player jumping animation?
29
+ *
30
+ * You can use this function to take the player to your custom stage.
31
+ *
32
+ * Under the hood, the custom trapdoor is represented by a decoration grid entity and is manually
33
+ * respawned every time the player re-enters the room.
34
+ *
35
+ * @param gridIndexOrPosition The location in the room to spawn the 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).
41
+ * @param anm2Path Optional. The path to the anm2 file to use. By default, the vanilla trapdoor anm2
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.
46
+ */
47
+ export function spawnCustomTrapdoor(
48
+ gridIndexOrPosition: int | Vector,
49
+ destination?: [stage: LevelStage, stageType: StageType] | string,
50
+ anm2Path = "gfx/grid/door_11_trapdoor.anm2",
51
+ spawnOpen?: boolean,
52
+ ): GridEntity {
53
+ errorIfFeaturesNotInitialized(CUSTOM_TRAPDOOR_FEATURE_NAME);
54
+
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(
63
+ GridEntityTypeCustom.TRAPDOOR_CUSTOM,
64
+ gridIndexOrPosition,
65
+ GridCollisionClass.NONE,
66
+ anm2Path,
67
+ TrapdoorAnimation.OPENED,
68
+ );
69
+
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);
89
+ }
90
+
91
+ return gridEntity;
92
+ }
93
+
94
+ function getDefaultDestination(): [stage: LevelStage, stageType: StageType] {
95
+ const nextStage = getNextStage();
96
+ const nextStageType = getNextStageType();
97
+
98
+ return [nextStage, nextStageType];
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
+ }