isaacscript-common 6.20.1 → 6.22.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 (122) hide show
  1. package/dist/constants.d.ts +6 -0
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.lua +4 -0
  4. package/dist/constantsFirstLast.d.ts +2 -2
  5. package/dist/constantsFirstLast.lua +2 -2
  6. package/dist/enums/RockAltType.d.ts +12 -1
  7. package/dist/enums/RockAltType.d.ts.map +1 -1
  8. package/dist/enums/RockAltType.lua +4 -2
  9. package/dist/enums/private/StageTravelState.d.ts +4 -3
  10. package/dist/enums/private/StageTravelState.d.ts.map +1 -1
  11. package/dist/enums/private/StageTravelState.lua +6 -4
  12. package/dist/features/customStage/backdrop.d.ts.map +1 -1
  13. package/dist/features/customStage/backdrop.lua +3 -2
  14. package/dist/features/customStage/customStageConstants.d.ts +11 -0
  15. package/dist/features/customStage/customStageConstants.d.ts.map +1 -1
  16. package/dist/features/customStage/customStageConstants.lua +10 -0
  17. package/dist/features/customStage/customStageGridEntities.d.ts +1 -0
  18. package/dist/features/customStage/customStageGridEntities.d.ts.map +1 -1
  19. package/dist/features/customStage/customStageGridEntities.lua +65 -23
  20. package/dist/features/customStage/exports.d.ts +17 -3
  21. package/dist/features/customStage/exports.d.ts.map +1 -1
  22. package/dist/features/customStage/exports.lua +65 -46
  23. package/dist/features/customStage/init.d.ts.map +1 -1
  24. package/dist/features/customStage/init.lua +7 -12
  25. package/dist/features/customStage/shadows.d.ts.map +1 -1
  26. package/dist/features/customStage/shadows.lua +2 -1
  27. package/dist/features/customStage/streakText.d.ts +0 -7
  28. package/dist/features/customStage/streakText.d.ts.map +1 -1
  29. package/dist/features/customStage/streakText.lua +52 -85
  30. package/dist/features/customStage/v.d.ts +13 -0
  31. package/dist/features/customStage/v.d.ts.map +1 -1
  32. package/dist/features/customStage/v.lua +6 -1
  33. package/dist/features/customStage/versusScreen.d.ts.map +1 -1
  34. package/dist/features/customStage/versusScreen.lua +32 -1
  35. package/dist/features/customTrapdoor/blackSprite.d.ts.map +1 -1
  36. package/dist/features/customTrapdoor/blackSprite.lua +2 -1
  37. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts +4 -0
  38. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts.map +1 -1
  39. package/dist/features/customTrapdoor/exports.d.ts +11 -7
  40. package/dist/features/customTrapdoor/exports.d.ts.map +1 -1
  41. package/dist/features/customTrapdoor/exports.lua +6 -5
  42. package/dist/features/customTrapdoor/init.d.ts.map +1 -1
  43. package/dist/features/customTrapdoor/init.lua +35 -20
  44. package/dist/features/customTrapdoor/touched.lua +1 -1
  45. package/dist/features/customTrapdoor/v.d.ts +2 -2
  46. package/dist/features/customTrapdoor/v.d.ts.map +1 -1
  47. package/dist/features/extraConsoleCommands/commandsSubroutines.d.ts.map +1 -1
  48. package/dist/features/extraConsoleCommands/commandsSubroutines.lua +3 -3
  49. package/dist/features/playerInventory.d.ts +7 -0
  50. package/dist/features/playerInventory.d.ts.map +1 -1
  51. package/dist/features/playerInventory.lua +7 -0
  52. package/dist/features/saveDataManager/exports.d.ts +3 -0
  53. package/dist/features/saveDataManager/exports.d.ts.map +1 -1
  54. package/dist/features/saveDataManager/exports.lua +3 -0
  55. package/dist/functions/collectibleSet.d.ts +1 -1
  56. package/dist/functions/collectibleSet.lua +1 -1
  57. package/dist/functions/doors.d.ts +10 -0
  58. package/dist/functions/doors.d.ts.map +1 -1
  59. package/dist/functions/doors.lua +6 -0
  60. package/dist/functions/levelGrid.lua +1 -1
  61. package/dist/functions/log.d.ts +1 -15
  62. package/dist/functions/log.d.ts.map +1 -1
  63. package/dist/functions/log.lua +3 -218
  64. package/dist/functions/logEntities.d.ts +16 -0
  65. package/dist/functions/logEntities.d.ts.map +1 -0
  66. package/dist/functions/logEntities.lua +220 -0
  67. package/dist/functions/rockAlt.d.ts +6 -5
  68. package/dist/functions/rockAlt.d.ts.map +1 -1
  69. package/dist/functions/rockAlt.lua +147 -18
  70. package/dist/functions/roomData.d.ts +1 -1
  71. package/dist/functions/roomData.d.ts.map +1 -1
  72. package/dist/functions/roomTransition.d.ts +26 -0
  73. package/dist/functions/roomTransition.d.ts.map +1 -0
  74. package/dist/functions/roomTransition.lua +75 -0
  75. package/dist/functions/rooms.d.ts +36 -35
  76. package/dist/functions/rooms.d.ts.map +1 -1
  77. package/dist/functions/rooms.lua +94 -99
  78. package/dist/index.d.ts +1 -0
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.lua +8 -0
  81. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts +2 -2
  82. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts.map +1 -1
  83. package/dist/objects/backdropTypeToRockAltType.lua +3 -3
  84. package/dist/types/TrapdoorDestination.d.ts +3 -0
  85. package/dist/types/TrapdoorDestination.d.ts.map +1 -0
  86. package/dist/types/TrapdoorDestination.lua +2 -0
  87. package/package.json +2 -2
  88. package/src/constants.ts +6 -0
  89. package/src/constantsFirstLast.ts +2 -2
  90. package/src/enums/RockAltType.ts +14 -1
  91. package/src/enums/private/StageTravelState.ts +2 -1
  92. package/src/features/customStage/backdrop.ts +2 -1
  93. package/src/features/customStage/customStageConstants.ts +16 -0
  94. package/src/features/customStage/customStageGridEntities.ts +61 -0
  95. package/src/features/customStage/exports.ts +81 -42
  96. package/src/features/customStage/init.ts +7 -18
  97. package/src/features/customStage/shadows.ts +2 -1
  98. package/src/features/customStage/streakText.ts +59 -96
  99. package/src/features/customStage/v.ts +17 -0
  100. package/src/features/customStage/versusScreen.ts +29 -0
  101. package/src/features/customTrapdoor/blackSprite.ts +6 -1
  102. package/src/features/customTrapdoor/customTrapdoorConstants.ts +4 -0
  103. package/src/features/customTrapdoor/exports.ts +8 -6
  104. package/src/features/customTrapdoor/init.ts +55 -23
  105. package/src/features/customTrapdoor/touched.ts +4 -1
  106. package/src/features/customTrapdoor/v.ts +2 -5
  107. package/src/features/extraConsoleCommands/commandsSubroutines.ts +4 -1
  108. package/src/features/playerInventory.ts +7 -0
  109. package/src/features/saveDataManager/exports.ts +3 -0
  110. package/src/functions/collectibleSet.ts +1 -1
  111. package/src/functions/doors.ts +10 -0
  112. package/src/functions/levelGrid.ts +1 -1
  113. package/src/functions/log.ts +1 -279
  114. package/src/functions/logEntities.ts +276 -0
  115. package/src/functions/rockAlt.ts +147 -19
  116. package/src/functions/roomData.ts +1 -1
  117. package/src/functions/roomTransition.ts +78 -0
  118. package/src/functions/rooms.ts +104 -107
  119. package/src/index.ts +1 -0
  120. package/src/interfaces/private/CustomTrapdoorDescription.ts +2 -2
  121. package/src/objects/backdropTypeToRockAltType.ts +3 -3
  122. package/src/types/TrapdoorDestination.ts +5 -0
@@ -8,55 +8,111 @@ import {
8
8
  import { game } from "../../cachedClasses";
9
9
  import { reorderedCallbacksSetStage } from "../../callbacks/reorderedCallbacks";
10
10
  import { getEntityIDFromConstituents } from "../../functions/entities";
11
- import { log, logError } from "../../functions/log";
11
+ import { logError } from "../../functions/log";
12
12
  import { newRNG } from "../../functions/rng";
13
- import { getRoomDataForTypeVariant, getRooms } from "../../functions/rooms";
13
+ import {
14
+ getRoomDataForTypeVariant,
15
+ getRoomsInGrid,
16
+ } from "../../functions/rooms";
14
17
  import { setStage } from "../../functions/stage";
18
+ import { CustomStage } from "../../interfaces/CustomStage";
15
19
  import { getRandomCustomStageRoom } from "./customStageUtils";
16
- import { topStreakTextStart } from "./streakText";
17
20
  import v, {
18
21
  customBossPNGPaths,
19
22
  customStageCachedRoomData,
20
23
  customStagesMap,
21
24
  } from "./v";
22
25
 
23
- const DEFAULT_BASE_STAGE = LevelStage.BASEMENT_2;
24
- const DEFAULT_BASE_STAGE_TYPE = StageType.ORIGINAL;
26
+ export const DEFAULT_BASE_STAGE = LevelStage.BASEMENT_2;
27
+ export const DEFAULT_BASE_STAGE_TYPE = StageType.ORIGINAL;
28
+
29
+ export const INVALID_STAGE_VALUE = -1 as LevelStage;
25
30
 
26
31
  /**
27
32
  * Helper function to warp to a custom stage/level.
28
33
  *
29
34
  * Custom stages/levels must first be defined in the "tsconfig.json" file. See the documentation for
30
35
  * more details: https://isaacscript.github.io/main/custom-stages/
36
+ *
37
+ * @param name The name of the custom stage, corresponding to what is in the "tsconfig.json" file.
38
+ * @param firstFloor Optional. Whether to go to the first floor or the second floor. For example, if
39
+ * you have a custom stage emulating Caves, then the first floor would be Caves 1,
40
+ * and the second floor would be Caves 2. Default is true.
41
+ * @param verbose Optional. Whether to log additional information about the rooms that are chosen.
42
+ * Default is false.
31
43
  */
32
- export function setCustomStage(name: string, verbose = false): void {
44
+ export function setCustomStage(
45
+ name: string,
46
+ firstFloor = true,
47
+ verbose = false,
48
+ ): void {
33
49
  const customStage = customStagesMap.get(name);
34
50
  if (customStage === undefined) {
35
51
  error(
36
- `Failed to set the custom stage of "${name}" because it was not found in the custom stages map. (Try restarting IsaacScript / recompiling the mod, and try again. If that does not work, you probably forgot to define it in your "tsconfig.json" file.) See the website for more details on how to set up custom stages.`,
52
+ `Failed to set the custom stage of "${name}" because it was not found in the custom stages map. (Try restarting IsaacScript / recompiling the mod / restarting the game, and try again. If that does not work, you probably forgot to define it in your "tsconfig.json" file.) See the website for more details on how to set up custom stages.`,
37
53
  );
38
54
  }
39
55
 
40
56
  const level = game.GetLevel();
41
- const startingRoomGridIndex = level.GetStartingRoomIndex();
57
+ const stage = level.GetStage();
42
58
  const seeds = game.GetSeeds();
43
59
  const startSeed = seeds.GetStartSeed();
44
60
  const rng = newRNG(startSeed);
45
61
 
46
62
  v.run.currentCustomStage = customStage;
63
+ v.run.firstFloor = firstFloor;
47
64
 
48
- const baseStage =
65
+ // Before changing the stage, we have to revert the bugged stage, if necessary. This prevents the
66
+ // bug where the backdrop will not spawn.
67
+ if (stage === INVALID_STAGE_VALUE) {
68
+ level.SetStage(LevelStage.BASEMENT_1, StageType.ORIGINAL);
69
+ }
70
+
71
+ let baseStage: int =
49
72
  customStage.baseStage === undefined
50
73
  ? DEFAULT_BASE_STAGE
51
- : (customStage.baseStage as LevelStage);
74
+ : customStage.baseStage;
75
+ if (!firstFloor) {
76
+ baseStage++;
77
+ }
78
+
52
79
  const baseStageType =
53
80
  customStage.baseStageType === undefined
54
81
  ? DEFAULT_BASE_STAGE_TYPE
55
82
  : (customStage.baseStageType as StageType);
56
- setStage(baseStage, baseStageType);
57
83
 
58
- // Now, we need to pick a custom room for each vanilla room.
59
- for (const room of getRooms()) {
84
+ const reseed = (stage as int) >= baseStage;
85
+
86
+ setStage(baseStage as LevelStage, baseStageType, reseed);
87
+
88
+ setStageRoomsData(customStage, rng, verbose);
89
+
90
+ // Set the stage to an invalid value, which will prevent the walls and floors from loading.
91
+ // Furthermore, we must use `StageType.WRATH_OF_THE_LAMB` instead of `StageType.ORIGINAL` or else
92
+ // the walls will not render properly. DeadInfinity suspects that this might be because it is
93
+ // trying to use the Dark Room's backdrop (instead of The Chest).
94
+ const targetStage = INVALID_STAGE_VALUE;
95
+ const targetStageType = StageType.WRATH_OF_THE_LAMB;
96
+ level.SetStage(targetStage, targetStageType);
97
+ reorderedCallbacksSetStage(targetStage, targetStageType);
98
+
99
+ // We must reload the current room in order for the `Level.SetStage` method to take effect.
100
+ // Furthermore, we need to cancel the queued warp to the `GridRoom.DEBUG` room. We can accomplish
101
+ // both of these things by initiating a room transition to an arbitrary room. However, we rely on
102
+ // the parent function to do this, since for normal purposes, we need to initiate a room
103
+ // transition for the pixelation effect.
104
+ }
105
+
106
+ /** Pick a custom room for each vanilla room. */
107
+ function setStageRoomsData(
108
+ customStage: CustomStage,
109
+ rng: RNG,
110
+ verbose: boolean,
111
+ ) {
112
+ const level = game.GetLevel();
113
+ const startingRoomGridIndex = level.GetStartingRoomIndex();
114
+
115
+ for (const room of getRoomsInGrid()) {
60
116
  // The starting floor of each room should stay empty.
61
117
  if (room.SafeGridIndex === startingRoomGridIndex) {
62
118
  continue;
@@ -73,7 +129,7 @@ export function setCustomStage(name: string, verbose = false): void {
73
129
  // special rooms.)
74
130
  if (roomType === RoomType.DEFAULT) {
75
131
  logError(
76
- `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) for custom stage: ${name}`,
132
+ `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) for custom stage: ${customStage.name}`,
77
133
  );
78
134
  }
79
135
  continue;
@@ -83,7 +139,7 @@ export function setCustomStage(name: string, verbose = false): void {
83
139
  const roomDoorSlotFlagMap = roomShapeMap.get(roomShape);
84
140
  if (roomDoorSlotFlagMap === undefined) {
85
141
  logError(
86
- `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) + RoomShape.${RoomShape[roomShape]} (${roomShape}) for custom stage: ${name}`,
142
+ `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) + RoomShape.${RoomShape[roomShape]} (${roomShape}) for custom stage: ${customStage.name}`,
87
143
  );
88
144
  continue;
89
145
  }
@@ -92,7 +148,7 @@ export function setCustomStage(name: string, verbose = false): void {
92
148
  const roomsMetadata = roomDoorSlotFlagMap.get(doorSlotFlags);
93
149
  if (roomsMetadata === undefined) {
94
150
  logError(
95
- `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) + RoomShape.${RoomShape[roomShape]} (${roomShape}) + DoorSlotFlags ${doorSlotFlags} for custom stage: ${name}`,
151
+ `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) + RoomShape.${RoomShape[roomShape]} (${roomShape}) + DoorSlotFlags ${doorSlotFlags} for custom stage: ${customStage.name}`,
96
152
  );
97
153
  continue;
98
154
  }
@@ -109,7 +165,7 @@ export function setCustomStage(name: string, verbose = false): void {
109
165
  );
110
166
  if (newRoomData === undefined) {
111
167
  logError(
112
- `Failed to get the room data for room variant ${randomRoom.variant} for custom stage: ${name}`,
168
+ `Failed to get the room data for room variant ${randomRoom.variant} for custom stage: ${customStage.name}`,
113
169
  );
114
170
  continue;
115
171
  }
@@ -119,31 +175,6 @@ export function setCustomStage(name: string, verbose = false): void {
119
175
 
120
176
  room.Data = newRoomData;
121
177
  }
122
-
123
- // Set the stage to an invalid value, which will prevent the walls and floors from loading. We
124
- // must use `StageType.WRATH_OF_THE_LAMB` instead of `StageType.ORIGINAL` or else the walls will
125
- // not render properly. DeadInfinity suspects that this might be because it is trying to use the
126
- // Dark Room's backdrop (instead of The Chest).
127
- const stage = -1 as LevelStage;
128
- const stageType = StageType.WRATH_OF_THE_LAMB;
129
- level.SetStage(stage, stageType);
130
- reorderedCallbacksSetStage(stage, stageType);
131
-
132
- // We must reload the current room in order for the `Level.SetStage` method to take effect.
133
- // Furthermore, we need to cancel the queued warp to the `GridRoom.DEBUG` room. We can accomplish
134
- // both of these things by initiating a room transition to an arbitrary room. However, we rely on
135
- // the parent function to do this, since for normal purposes, we need to initiate a room
136
- // transition for the pixelation effect.
137
- }
138
-
139
- export function setCustomStageDebug(): void {
140
- const customStage = v.run.currentCustomStage;
141
- if (customStage === null) {
142
- log("No custom stage is currently loaded.");
143
- return;
144
- }
145
-
146
- topStreakTextStart();
147
178
  }
148
179
 
149
180
  /**
@@ -181,3 +212,11 @@ export function registerCustomBoss(
181
212
  const entityID = getEntityIDFromConstituents(entityType, variant, subType);
182
213
  customBossPNGPaths.set(entityID, [namePNGPath, portraitPNGPath]);
183
214
  }
215
+
216
+ /**
217
+ * Helper function to disable the custom stage. This is typically called before taking the player to
218
+ * a vanilla floor.
219
+ */
220
+ export function disableCustomStage(): void {
221
+ v.run.currentCustomStage = null;
222
+ }
@@ -18,6 +18,7 @@ import {
18
18
  import { saveDataManager } from "../saveDataManager/exports";
19
19
  import { setBackdrop } from "./backdrop";
20
20
  import {
21
+ convertVanillaTrapdoors,
21
22
  removeUrnRewards,
22
23
  setCustomDecorationGraphics,
23
24
  setCustomDoorGraphics,
@@ -26,12 +27,7 @@ import {
26
27
  } from "./customStageGridEntities";
27
28
  import * as metadataJSON from "./metadata.json"; // This will correspond to "metadata.lua" at run-time.
28
29
  import { setShadows } from "./shadows";
29
- import {
30
- streakTextGetShaderParams,
31
- streakTextInit,
32
- streakTextPostGameStarted,
33
- streakTextPostRender,
34
- } from "./streakText";
30
+ import { streakTextGetShaderParams, streakTextPostRender } from "./streakText";
35
31
  import v, { customStagesMap } from "./v";
36
32
  import {
37
33
  playVersusScreenAnimation,
@@ -47,12 +43,10 @@ export function customStageInit(mod: ModUpgraded): void {
47
43
  }
48
44
 
49
45
  saveDataManager("customStage", v);
50
- streakTextInit();
51
46
  versusScreenInit();
52
47
 
53
48
  mod.AddCallback(ModCallback.POST_RENDER, postRender); // 2
54
49
  mod.AddCallback(ModCallback.POST_CURSE_EVAL, postCurseEval); // 12
55
- mod.AddCallback(ModCallback.POST_GAME_STARTED, postGameStarted); // 15
56
50
  mod.AddCallback(ModCallback.GET_SHADER_PARAMS, getShaderParams); // 21
57
51
  mod.AddCallbackCustom(
58
52
  ModCallbackCustom.POST_GRID_ENTITY_BROKEN,
@@ -61,7 +55,7 @@ export function customStageInit(mod: ModUpgraded): void {
61
55
  );
62
56
  mod.AddCallbackCustom(
63
57
  ModCallbackCustom.POST_GRID_ENTITY_INIT,
64
- postGridEntityBrokenInit,
58
+ postGridEntityInit,
65
59
  );
66
60
  mod.AddCallbackCustom(
67
61
  ModCallbackCustom.POST_NEW_ROOM_REORDERED,
@@ -150,19 +144,13 @@ function postCurseEval(
150
144
  }
151
145
 
152
146
  // Prevent XL floors on custom stages, since the streak text will not work properly.
153
- if (hasFlag(curses, LevelCurse.MAZE)) {
154
- return removeFlag(curses, LevelCurse.MAZE);
147
+ if (hasFlag(curses, LevelCurse.LABYRINTH)) {
148
+ return removeFlag(curses, LevelCurse.LABYRINTH);
155
149
  }
156
150
 
157
151
  return undefined;
158
152
  }
159
153
 
160
- // ModCallback.POST_GAME_STARTED (15)
161
- function postGameStarted() {
162
- // We don't early return here because we need to unconditionally reset the sprites.
163
- streakTextPostGameStarted();
164
- }
165
-
166
154
  // ModCallback.GET_SHADER_PARAMS (22)
167
155
  function getShaderParams(
168
156
  shaderName: string,
@@ -188,7 +176,7 @@ function postGridEntityBrokenRockAlt(gridEntity: GridEntity) {
188
176
  }
189
177
 
190
178
  // ModCallbackCustom.POST_GRID_ENTITY_INIT
191
- function postGridEntityBrokenInit(gridEntity: GridEntity) {
179
+ function postGridEntityInit(gridEntity: GridEntity) {
192
180
  const customStage = v.run.currentCustomStage;
193
181
  if (customStage === null) {
194
182
  return;
@@ -198,6 +186,7 @@ function postGridEntityBrokenInit(gridEntity: GridEntity) {
198
186
  setCustomRockGraphics(customStage, gridEntity);
199
187
  setCustomPitGraphics(customStage, gridEntity);
200
188
  setCustomDoorGraphics(customStage, gridEntity);
189
+ convertVanillaTrapdoors(customStage, gridEntity);
201
190
  }
202
191
 
203
192
  // ModCallbackCustom.POST_NEW_ROOM_REORDERED
@@ -17,6 +17,7 @@ type ShadowAnimation = "1x1" | "1x2" | "2x1" | "2x2";
17
17
  * time passes, like most other effects.
18
18
  */
19
19
  const SHADOW_EFFECT_VARIANT = EffectVariant.LADDER;
20
+ const SHADOW_EFFECT_SUBTYPE = 102;
20
21
 
21
22
  /** The animation comes from StageAPI. */
22
23
  const ROOM_SHAPE_TO_SHADOW_ANIMATION: {
@@ -57,7 +58,7 @@ export function setShadows(customStage: CustomStage): void {
57
58
  // rendering throughout this animation.)
58
59
  const shadowEffect = spawnEffectWithSeed(
59
60
  SHADOW_EFFECT_VARIANT,
60
- 0,
61
+ SHADOW_EFFECT_SUBTYPE,
61
62
  centerPos,
62
63
  1 as Seed,
63
64
  );
@@ -7,15 +7,12 @@ import {
7
7
  getScreenTopCenterPos,
8
8
  } from "../../functions/ui";
9
9
  import { CustomStage } from "../../interfaces/CustomStage";
10
+ import {
11
+ UIStreakAnimation,
12
+ UI_STREAK_ANIMATION_END_FRAMES,
13
+ } from "./customStageConstants";
10
14
  import v from "./v";
11
15
 
12
- enum UIStreakAnimation {
13
- TEXT = "Text",
14
- TEXT_IN = "TextIn",
15
- TEXT_OUT = "TextOut",
16
- TEXT_STAY = "TextStay",
17
- }
18
-
19
16
  /** This must match the name of the shader in "shaders.xml". */
20
17
  const EMPTY_SHADER_NAME = "IsaacScript-RenderAboveHUD";
21
18
 
@@ -29,7 +26,7 @@ const TEXT_STAY_FRAME = 8;
29
26
  const TEXT_OUT_FRAME = 60;
30
27
 
31
28
  /** This matches the offset that the vanilla game uses; determined via trial and error. */
32
- const STREAK_SPRITE_TOP_OFFSET = Vector(0, 48.25);
29
+ const STREAK_SPRITE_TOP_OFFSET = Vector(0, 47);
33
30
 
34
31
  /** This matches the offset that the vanilla game uses; determined via trial and error. */
35
32
  const STREAK_SPRITE_BOTTOM_OFFSET = Vector(0, -48.25);
@@ -47,9 +44,6 @@ const STREAK_TEXT_BOTTOM_Y_OFFSET = -9;
47
44
  */
48
45
  const NUM_RENDER_FRAMES_MAP_HELD_BEFORE_STREAK_TEXT = 11;
49
46
 
50
- /** Corresponds to the vanilla value; determined through trial and error. */
51
- const TEXT_PLAYBACK_SPEED = 0.5;
52
-
53
47
  /** Taken from StageAPI. */
54
48
  const TEXT_IN_ADJUSTMENTS = [-800, -639, -450, -250, -70, 10, 6, 3];
55
49
 
@@ -82,33 +76,6 @@ const TEXT_OUT_SCALES = [
82
76
  Vector(3, 0.2),
83
77
  ];
84
78
 
85
- /**
86
- * We do not actually need to render this sprite at all. It's only purpose is to be an invisible
87
- * reference for when and where we need to render the stage's name. Thus, we specify "false" for
88
- * "loadGraphics", but still manage playing its animations in the code below.
89
- */
90
- const topStreakSprite = Sprite();
91
-
92
- /**
93
- * We do not actually need to render this sprite at all. It's only purpose is to be an invisible
94
- * reference for when and where we need to render the stage's name. Thus, we specify "false" for
95
- * "loadGraphics", but still manage playing its animations in the code below.
96
- */
97
- const bottomStreakSprite = Sprite();
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
- }
111
-
112
79
  // ModCallback.POST_RENDER (2)
113
80
  export function streakTextPostRender(): void {
114
81
  // The top streak only plays when the player arrives on the floor (or continues a game from the
@@ -124,7 +91,7 @@ export function streakTextPostRender(): void {
124
91
  function checkEndTopStreakText() {
125
92
  if (
126
93
  v.run.topStreakTextStartedRenderFrame === null ||
127
- !topStreakSprite.IsPlaying(UIStreakAnimation.TEXT_STAY)
94
+ v.run.topStreakText.animation !== UIStreakAnimation.TEXT_STAY
128
95
  ) {
129
96
  return;
130
97
  }
@@ -133,7 +100,10 @@ function checkEndTopStreakText() {
133
100
  const elapsedFrames =
134
101
  renderFrameCount - v.run.topStreakTextStartedRenderFrame;
135
102
  if (elapsedFrames >= 115) {
136
- playTextOut(topStreakSprite);
103
+ v.run.topStreakText.animation = UIStreakAnimation.TEXT;
104
+ // We adjust by the frame backwards by an arbitrary amount to roughly align with the speed of
105
+ // the vanilla animation.
106
+ v.run.topStreakText.frame = TEXT_OUT_FRAME - 2;
137
107
  }
138
108
  }
139
109
 
@@ -165,7 +135,7 @@ function trackMapInputPressed() {
165
135
  * slides in from the left.
166
136
  */
167
137
  function checkStartBottomStreakText() {
168
- if (bottomStreakSprite.IsPlaying()) {
138
+ if (v.run.bottomStreakText.animation !== UIStreakAnimation.NONE) {
169
139
  return;
170
140
  }
171
141
 
@@ -180,7 +150,8 @@ function checkStartBottomStreakText() {
180
150
  const gameFrameCount = game.GetFrameCount();
181
151
  const elapsedFrames = gameFrameCount - earliestFrame;
182
152
  if (elapsedFrames >= NUM_RENDER_FRAMES_MAP_HELD_BEFORE_STREAK_TEXT) {
183
- bottomStreakSprite.Play(UIStreakAnimation.TEXT, true);
153
+ v.run.bottomStreakText.animation = UIStreakAnimation.TEXT;
154
+ v.run.bottomStreakText.frame = 0;
184
155
  }
185
156
  }
186
157
 
@@ -189,7 +160,7 @@ function checkStartBottomStreakText() {
189
160
  * right.
190
161
  */
191
162
  function checkEndBottomStreakText() {
192
- if (!bottomStreakSprite.IsPlaying(UIStreakAnimation.TEXT_STAY)) {
163
+ if (v.run.bottomStreakText.animation !== UIStreakAnimation.TEXT_STAY) {
193
164
  return;
194
165
  }
195
166
 
@@ -197,16 +168,13 @@ function checkEndBottomStreakText() {
197
168
  ...v.run.controllerIndexPushingMapRenderFrame.values(),
198
169
  ];
199
170
  if (pushedMapFrames.length === 0) {
200
- playTextOut(bottomStreakSprite);
171
+ v.run.bottomStreakText.animation = UIStreakAnimation.TEXT;
172
+ // We adjust by the frame backwards by an arbitrary amount to roughly align with the speed of
173
+ // the vanilla animation.
174
+ v.run.bottomStreakText.frame = TEXT_OUT_FRAME - 2;
201
175
  }
202
176
  }
203
177
 
204
- // ModCallback.POST_GAME_STARTED (15)
205
- export function streakTextPostGameStarted(): void {
206
- topStreakSprite.Stop();
207
- bottomStreakSprite.Stop();
208
- }
209
-
210
178
  // ModCallback.GET_SHADER_PARAMS (22)
211
179
  export function streakTextGetShaderParams(
212
180
  customStage: CustomStage,
@@ -218,27 +186,44 @@ export function streakTextGetShaderParams(
218
186
 
219
187
  const topCenterPos = getScreenTopCenterPos();
220
188
  const topStreakPosition = topCenterPos.add(STREAK_SPRITE_TOP_OFFSET);
221
- renderSprite(customStage, topStreakSprite, topStreakPosition);
189
+ renderStreakText(customStage, v.run.topStreakText, topStreakPosition);
222
190
 
223
191
  const bottomCenterPos = getScreenBottomCenterPos();
224
192
  const bottomStreakPosition = bottomCenterPos.add(STREAK_SPRITE_BOTTOM_OFFSET);
225
- renderSprite(customStage, bottomStreakSprite, bottomStreakPosition);
193
+ renderStreakText(customStage, v.run.bottomStreakText, bottomStreakPosition);
226
194
  }
227
195
 
228
- function renderSprite(
196
+ function renderStreakText(
229
197
  customStage: CustomStage,
230
- sprite: Sprite,
198
+ streakText: { animation: UIStreakAnimation; frame: int; pauseFrame: boolean },
231
199
  position: Vector,
232
200
  ) {
233
- sprite.Update();
234
- if (!sprite.IsPlaying()) {
201
+ if (streakText.animation === UIStreakAnimation.NONE) {
202
+ return;
203
+ }
204
+
205
+ if (streakText.animation !== UIStreakAnimation.TEXT_STAY) {
206
+ const { pauseFrame } = streakText;
207
+ streakText.pauseFrame = !streakText.pauseFrame;
208
+
209
+ if (!pauseFrame) {
210
+ streakText.frame++;
211
+ }
212
+ }
213
+
214
+ const endFrame = UI_STREAK_ANIMATION_END_FRAMES[streakText.animation];
215
+ if (streakText.frame > endFrame) {
216
+ streakText.animation = UIStreakAnimation.NONE;
217
+ streakText.frame = 0;
235
218
  return;
236
219
  }
237
220
 
238
- const animation = sprite.GetAnimation() as UIStreakAnimation;
239
- const frame = sprite.GetFrame();
240
- if (animation === UIStreakAnimation.TEXT && frame === TEXT_STAY_FRAME) {
241
- sprite.Play(UIStreakAnimation.TEXT_STAY, true);
221
+ if (
222
+ streakText.animation === UIStreakAnimation.TEXT &&
223
+ streakText.frame === TEXT_STAY_FRAME
224
+ ) {
225
+ streakText.animation = UIStreakAnimation.TEXT_STAY;
226
+ streakText.frame = 0;
242
227
  }
243
228
 
244
229
  const isPaused = game.IsPaused();
@@ -248,39 +233,22 @@ function renderSprite(
248
233
 
249
234
  const font = fonts.upheaval;
250
235
  const { name } = customStage;
251
- const length = font.GetStringWidthUTF8(name);
236
+ const numberSuffix = v.run.firstFloor ? "I" : "II";
237
+ const nameWithNumberSuffix = `${name} ${numberSuffix}`;
238
+ const length = font.GetStringWidthUTF8(nameWithNumberSuffix);
252
239
  const centeredX = position.X - length / 2;
253
240
 
254
241
  let adjustment = 0;
255
242
  let scale = VectorOne;
256
- switch (animation) {
257
- case UIStreakAnimation.TEXT: {
258
- if (frame < TEXT_STAY_FRAME) {
259
- adjustment = TEXT_IN_ADJUSTMENTS[frame] ?? 0;
260
- scale = TEXT_IN_SCALES[frame] ?? VectorOne;
261
- } else {
262
- const adjustedFrame = frame - TEXT_OUT_FRAME;
263
- adjustment = TEXT_OUT_ADJUSTMENTS[adjustedFrame] ?? 0;
264
- scale = TEXT_OUT_SCALES[adjustedFrame] ?? VectorOne;
265
- }
266
243
 
267
- break;
268
- }
269
-
270
- case UIStreakAnimation.TEXT_IN: {
271
- adjustment = TEXT_IN_ADJUSTMENTS[frame] ?? 0;
272
- scale = TEXT_IN_SCALES[frame] ?? VectorOne;
273
- break;
274
- }
275
-
276
- case UIStreakAnimation.TEXT_OUT: {
277
- adjustment = TEXT_OUT_ADJUSTMENTS[frame] ?? 0;
278
- scale = TEXT_OUT_SCALES[frame] ?? VectorOne;
279
- break;
280
- }
281
-
282
- default: {
283
- break;
244
+ if (streakText.animation === UIStreakAnimation.TEXT) {
245
+ if (streakText.frame < TEXT_STAY_FRAME) {
246
+ adjustment = TEXT_IN_ADJUSTMENTS[streakText.frame] ?? 0;
247
+ scale = TEXT_IN_SCALES[streakText.frame] ?? VectorOne;
248
+ } else {
249
+ const adjustedFrame = streakText.frame - TEXT_OUT_FRAME;
250
+ adjustment = TEXT_OUT_ADJUSTMENTS[adjustedFrame] ?? 0;
251
+ scale = TEXT_OUT_SCALES[adjustedFrame] ?? VectorOne;
284
252
  }
285
253
  }
286
254
 
@@ -288,7 +256,7 @@ function renderSprite(
288
256
  const adjustedY = position.Y + STREAK_TEXT_BOTTOM_Y_OFFSET;
289
257
 
290
258
  font.DrawStringScaled(
291
- name,
259
+ nameWithNumberSuffix,
292
260
  adjustedX,
293
261
  adjustedY,
294
262
  scale.X,
@@ -297,12 +265,6 @@ function renderSprite(
297
265
  );
298
266
  }
299
267
 
300
- function playTextOut(sprite: Sprite) {
301
- sprite.Play(UIStreakAnimation.TEXT, true);
302
- // We adjust by 2 to roughly align with the speed of the vanilla animation.
303
- sprite.SetFrame(TEXT_OUT_FRAME - 2);
304
- }
305
-
306
268
  export function topStreakTextStart(): void {
307
269
  const level = game.GetLevel();
308
270
  const renderFrameCount = Isaac.GetFrameCount();
@@ -311,6 +273,7 @@ export function topStreakTextStart(): void {
311
273
  level.ShowName(false);
312
274
 
313
275
  // Initiate the animation for the custom text.
276
+ v.run.topStreakText.animation = UIStreakAnimation.TEXT;
277
+ v.run.topStreakText.frame = 0;
314
278
  v.run.topStreakTextStartedRenderFrame = renderFrameCount;
315
- topStreakSprite.Play(UIStreakAnimation.TEXT, true);
316
279
  }
@@ -1,15 +1,32 @@
1
1
  import { ControllerIndex } from "isaac-typescript-definitions";
2
2
  import { CustomStage } from "../../interfaces/CustomStage";
3
+ import { UIStreakAnimation } from "./customStageConstants";
3
4
 
4
5
  const v = {
5
6
  run: {
6
7
  currentCustomStage: null as CustomStage | null,
8
+
9
+ /** Whether we are on e.g. Caves 1 or Caves 2. */
10
+ firstFloor: true,
11
+
7
12
  showingBossVersusScreen: false,
8
13
 
9
14
  /** Values are the render frame that the controller first pressed the map button. */
10
15
  controllerIndexPushingMapRenderFrame: new Map<ControllerIndex, int>(),
11
16
 
12
17
  topStreakTextStartedRenderFrame: null as int | null,
18
+
19
+ topStreakText: {
20
+ animation: UIStreakAnimation.NONE,
21
+ frame: 0,
22
+ pauseFrame: false,
23
+ },
24
+
25
+ bottomStreakText: {
26
+ animation: UIStreakAnimation.NONE,
27
+ frame: 0,
28
+ pauseFrame: false,
29
+ },
13
30
  },
14
31
 
15
32
  room: {
@@ -4,6 +4,7 @@ import {
4
4
  RoomType,
5
5
  SoundEffect,
6
6
  StageID,
7
+ StageType,
7
8
  } from "isaac-typescript-definitions";
8
9
  import { game, sfxManager } from "../../cachedClasses";
9
10
  import { arrayRemove } from "../../functions/array";
@@ -18,7 +19,13 @@ import { PLAYER_PORTRAIT_PNG_FILE_NAMES } from "../../objects/playerPortraitPNGF
18
19
  import { VERSUS_SCREEN_BACKGROUND_COLORS } from "../../objects/versusScreenBackgroundColors";
19
20
  import { VERSUS_SCREEN_DIRT_SPOT_COLORS } from "../../objects/versusScreenDirtSpotColors";
20
21
  import { pause, unpause } from "../pause";
22
+ import { runNextGameFrame } from "../runInNFrames";
21
23
  import { ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH } from "./customStageConstants";
24
+ import {
25
+ DEFAULT_BASE_STAGE,
26
+ DEFAULT_BASE_STAGE_TYPE,
27
+ INVALID_STAGE_VALUE,
28
+ } from "./exports";
22
29
  import v, { customBossPNGPaths } from "./v";
23
30
 
24
31
  const DEFAULT_CHARACTER = PlayerType.ISAAC;
@@ -102,12 +109,29 @@ export function versusScreenInit(): void {
102
109
  export function playVersusScreenAnimation(customStage: CustomStage): void {
103
110
  const room = game.GetRoom();
104
111
  const roomType = room.GetType();
112
+ const roomCleared = room.IsClear();
105
113
  const hud = game.GetHUD();
106
114
 
107
115
  if (roomType !== RoomType.BOSS) {
108
116
  return;
109
117
  }
110
118
 
119
+ if (roomCleared) {
120
+ return;
121
+ }
122
+
123
+ if (willVanillaVersusScreenPlay()) {
124
+ // Since we are on an invalid stage, the versus screen will have a completely black background.
125
+ // Revert to using the background from the default stage.
126
+ const level = game.GetLevel();
127
+ level.SetStage(DEFAULT_BASE_STAGE, DEFAULT_BASE_STAGE_TYPE);
128
+ runNextGameFrame(() => {
129
+ const futureLevel = game.GetLevel();
130
+ futureLevel.SetStage(INVALID_STAGE_VALUE, StageType.ORIGINAL);
131
+ });
132
+ return;
133
+ }
134
+
111
135
  v.run.showingBossVersusScreen = true;
112
136
 
113
137
  pause();
@@ -156,6 +180,11 @@ export function playVersusScreenAnimation(customStage: CustomStage): void {
156
180
  }
157
181
  }
158
182
 
183
+ function willVanillaVersusScreenPlay() {
184
+ const bosses = getBosses();
185
+ return bosses.some((boss) => boss.GetBossID() !== 0);
186
+ }
187
+
159
188
  /** Use the character of the 0th player. */
160
189
  function getPlayerPNGPaths(): [
161
190
  playerNamePNGPath: string,