isaacscript-common 6.8.0 → 6.10.1

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/features/customStage/customStageGridEntities.lua +2 -2
  2. package/dist/features/customStage/exports.d.ts.map +1 -1
  3. package/dist/features/customStage/exports.lua +5 -4
  4. package/dist/features/customStage/init.d.ts.map +1 -1
  5. package/dist/features/customStage/init.lua +12 -3
  6. package/dist/features/customStage/streakText.d.ts +4 -2
  7. package/dist/features/customStage/streakText.d.ts.map +1 -1
  8. package/dist/features/customStage/streakText.lua +216 -15
  9. package/dist/features/customStage/v.d.ts +4 -2
  10. package/dist/features/customStage/v.d.ts.map +1 -1
  11. package/dist/features/customStage/v.lua +9 -1
  12. package/dist/features/customStage/versusScreen.d.ts +1 -1
  13. package/dist/features/customStage/versusScreen.d.ts.map +1 -1
  14. package/dist/features/customStage/versusScreen.lua +2 -5
  15. package/dist/features/extraConsoleCommands/init.lua +1 -0
  16. package/dist/features/extraConsoleCommands/listCommands.d.ts +8 -0
  17. package/dist/features/extraConsoleCommands/listCommands.d.ts.map +1 -1
  18. package/dist/features/extraConsoleCommands/listCommands.lua +13 -0
  19. package/dist/features/saveDataManager/exports.d.ts.map +1 -1
  20. package/dist/features/saveDataManager/exports.lua +2 -4
  21. package/dist/features/saveDataManager/load.d.ts +1 -1
  22. package/dist/features/saveDataManager/load.d.ts.map +1 -1
  23. package/dist/features/saveDataManager/main.d.ts.map +1 -1
  24. package/dist/features/saveDataManager/main.lua +0 -1
  25. package/dist/features/saveDataManager/maps.d.ts +3 -3
  26. package/dist/features/saveDataManager/maps.d.ts.map +1 -1
  27. package/dist/features/saveDataManager/maps.lua +1 -4
  28. package/dist/features/saveDataManager/merge.d.ts +2 -2
  29. package/dist/features/saveDataManager/merge.d.ts.map +1 -1
  30. package/dist/features/saveDataManager/merge.lua +3 -3
  31. package/dist/features/saveDataManager/save.d.ts +1 -1
  32. package/dist/features/saveDataManager/save.d.ts.map +1 -1
  33. package/dist/features/saveDataManager/save.lua +1 -2
  34. package/dist/features/saveDataManager/{serializationBrand.d.ts → serializationBrands.d.ts} +1 -1
  35. package/dist/features/saveDataManager/serializationBrands.d.ts.map +1 -0
  36. package/dist/features/saveDataManager/{serializationBrand.lua → serializationBrands.lua} +0 -0
  37. package/dist/functions/array.d.ts.map +1 -1
  38. package/dist/functions/color.d.ts +1 -1
  39. package/dist/functions/color.d.ts.map +1 -1
  40. package/dist/functions/deepCopy.d.ts +1 -1
  41. package/dist/functions/deepCopy.lua +55 -36
  42. package/dist/functions/deepCopyTests.lua +20 -17
  43. package/dist/functions/entities.d.ts +1 -1
  44. package/dist/functions/entities.d.ts.map +1 -1
  45. package/dist/functions/input.d.ts.map +1 -1
  46. package/dist/functions/input.lua +8 -10
  47. package/dist/functions/jsonHelpers.d.ts +1 -1
  48. package/dist/functions/jsonHelpers.d.ts.map +1 -1
  49. package/dist/functions/kColor.d.ts +1 -1
  50. package/dist/functions/kColor.d.ts.map +1 -1
  51. package/dist/functions/log.d.ts +1 -1
  52. package/dist/functions/log.d.ts.map +1 -1
  53. package/dist/functions/log.lua +3 -1
  54. package/dist/functions/mergeTests.d.ts +2 -2
  55. package/dist/functions/mergeTests.lua +2 -2
  56. package/dist/functions/playerIndex.d.ts +1 -1
  57. package/dist/functions/playerIndex.lua +1 -1
  58. package/dist/functions/rng.d.ts +1 -1
  59. package/dist/functions/rng.d.ts.map +1 -1
  60. package/dist/functions/run.d.ts +8 -0
  61. package/dist/functions/run.d.ts.map +1 -1
  62. package/dist/functions/run.lua +15 -0
  63. package/dist/functions/table.d.ts +9 -9
  64. package/dist/functions/table.d.ts.map +1 -1
  65. package/dist/functions/table.lua +23 -21
  66. package/dist/functions/types.d.ts +1 -1
  67. package/dist/functions/types.d.ts.map +1 -1
  68. package/dist/functions/vector.d.ts +1 -1
  69. package/dist/functions/vector.d.ts.map +1 -1
  70. package/dist/interfaces/SaveData.d.ts +1 -1
  71. package/dist/interfaces/private/TSTLClassMetatable.d.ts +1 -1
  72. package/dist/interfaces/private/TSTLClassMetatable.d.ts.map +1 -1
  73. package/dist/lualib_bundle.lua +38 -7
  74. package/dist/types/PlayerIndex.d.ts +3 -2
  75. package/dist/types/PlayerIndex.d.ts.map +1 -1
  76. package/dist/types/private/IsaacAPIClass.d.ts +1 -1
  77. package/dist/types/private/IsaacAPIClass.d.ts.map +1 -1
  78. package/dist/types/private/SerializedIsaacAPIClass.d.ts +1 -1
  79. package/dist/types/private/SerializedIsaacAPIClass.d.ts.map +1 -1
  80. package/dist/types/private/TSTLClass.d.ts +1 -1
  81. package/dist/types/private/TSTLClass.d.ts.map +1 -1
  82. package/package.json +2 -2
  83. package/src/features/customStage/customStageGridEntities.ts +4 -4
  84. package/src/features/customStage/exports.ts +5 -3
  85. package/src/features/customStage/init.ts +19 -7
  86. package/src/features/customStage/streakText.ts +284 -19
  87. package/src/features/customStage/v.ts +6 -1
  88. package/src/features/customStage/versusScreen.ts +2 -5
  89. package/src/features/extraConsoleCommands/init.ts +1 -0
  90. package/src/features/extraConsoleCommands/listCommands.ts +14 -0
  91. package/src/features/saveDataManager/exports.ts +3 -8
  92. package/src/features/saveDataManager/load.ts +2 -3
  93. package/src/features/saveDataManager/main.ts +4 -5
  94. package/src/features/saveDataManager/maps.ts +3 -3
  95. package/src/features/saveDataManager/merge.ts +11 -11
  96. package/src/features/saveDataManager/save.ts +6 -6
  97. package/src/features/saveDataManager/{serializationBrand.ts → serializationBrands.ts} +0 -0
  98. package/src/functions/array.ts +2 -4
  99. package/src/functions/color.ts +3 -3
  100. package/src/functions/deepCopy.ts +77 -40
  101. package/src/functions/deepCopyTests.ts +45 -28
  102. package/src/functions/entities.ts +7 -7
  103. package/src/functions/input.ts +7 -8
  104. package/src/functions/isaacAPIClass.ts +3 -3
  105. package/src/functions/jsonHelpers.ts +3 -3
  106. package/src/functions/kColor.ts +3 -3
  107. package/src/functions/log.ts +7 -4
  108. package/src/functions/mergeTests.ts +24 -24
  109. package/src/functions/playerIndex.ts +1 -1
  110. package/src/functions/rng.ts +3 -3
  111. package/src/functions/run.ts +14 -0
  112. package/src/functions/table.ts +25 -23
  113. package/src/functions/tstlClass.ts +1 -1
  114. package/src/functions/types.ts +3 -1
  115. package/src/functions/vector.ts +3 -3
  116. package/src/interfaces/SaveData.ts +1 -1
  117. package/src/interfaces/private/TSTLClassMetatable.ts +1 -1
  118. package/src/types/PlayerIndex.ts +3 -2
  119. package/src/types/private/IsaacAPIClass.ts +1 -1
  120. package/src/types/private/SerializedIsaacAPIClass.ts +1 -1
  121. package/src/types/private/TSTLClass.ts +1 -1
  122. package/dist/features/saveDataManager/serializationBrand.d.ts.map +0 -1
@@ -24,7 +24,11 @@ import {
24
24
  } from "./customStageGridEntities";
25
25
  import * as metadataJSON from "./metadata.json"; // This will correspond to "metadata.lua" at run-time.
26
26
  import { setShadows } from "./shadows";
27
- import { streakTextGetShaderParams, streakTextPostRender } from "./streakText";
27
+ import {
28
+ streakTextGetShaderParams,
29
+ streakTextPostGameStarted,
30
+ streakTextPostRender,
31
+ } from "./streakText";
28
32
  import v, { customStagesMap } from "./v";
29
33
  import {
30
34
  playVersusScreenAnimation,
@@ -36,20 +40,17 @@ export function customStageInit(mod: ModUpgraded): void {
36
40
  initRoomTypeMaps();
37
41
 
38
42
  mod.AddCallback(ModCallback.POST_RENDER, postRender); // 2
39
-
43
+ mod.AddCallback(ModCallback.POST_GAME_STARTED, postGameStarted); // 15
40
44
  mod.AddCallback(ModCallback.GET_SHADER_PARAMS, getShaderParams); // 21
41
-
42
45
  mod.AddCallbackCustom(
43
46
  ModCallbackCustom.POST_GRID_ENTITY_BROKEN,
44
47
  postGridEntityBrokenRockAlt,
45
48
  GridEntityType.ROCK_ALT,
46
49
  );
47
-
48
50
  mod.AddCallbackCustom(
49
51
  ModCallbackCustom.POST_GRID_ENTITY_INIT,
50
52
  postGridEntityBrokenInit,
51
53
  );
52
-
53
54
  mod.AddCallbackCustom(
54
55
  ModCallbackCustom.POST_NEW_ROOM_REORDERED,
55
56
  postNewRoomReordered,
@@ -123,15 +124,26 @@ function postRender() {
123
124
  return;
124
125
  }
125
126
 
126
- streakTextPostRender(customStage);
127
+ streakTextPostRender();
127
128
  versusScreenPostRender();
128
129
  }
129
130
 
131
+ // ModCallback.POST_GAME_STARTED (15)
132
+ function postGameStarted() {
133
+ // We don't early return here because we need to unconditionally reset the sprites.
134
+ streakTextPostGameStarted();
135
+ }
136
+
130
137
  // ModCallback.GET_SHADER_PARAMS (22)
131
138
  function getShaderParams(
132
139
  shaderName: string,
133
140
  ): Record<string, unknown> | undefined {
134
- streakTextGetShaderParams(shaderName);
141
+ const customStage = v.run.currentCustomStage;
142
+ if (customStage === null) {
143
+ return;
144
+ }
145
+
146
+ streakTextGetShaderParams(customStage, shaderName);
135
147
  return undefined;
136
148
  }
137
149
 
@@ -1,43 +1,308 @@
1
+ import { ButtonAction, ControllerIndex } from "isaac-typescript-definitions";
1
2
  import { fonts, game } from "../../cachedClasses";
2
- import { KColorDefault } from "../../constants";
3
- import { getScreenBottomCenterPos } from "../../functions/ui";
4
- import { todo } from "../../functions/utils";
3
+ import { KColorDefault, VectorOne } from "../../constants";
4
+ import { getEnumValues } from "../../functions/enums";
5
+ import {
6
+ getScreenBottomCenterPos,
7
+ getScreenTopCenterPos,
8
+ } from "../../functions/ui";
5
9
  import { CustomStage } from "../../interfaces/CustomStage";
6
10
  import v from "./v";
7
11
 
12
+ enum UIStreakAnimation {
13
+ TEXT = "Text",
14
+ TEXT_IN = "TextIn",
15
+ TEXT_OUT = "TextOut",
16
+ TEXT_STAY = "TextStay",
17
+ }
18
+
8
19
  /** This must match the name of the shader in "shaders.xml". */
9
20
  const EMPTY_SHADER_NAME = "IsaacScript-RenderAboveHUD";
10
21
 
11
- /** This matches the offset that the vanilla game uses. */
12
- const STREAK_TEXT_BOTTOM_OFFSET = Vector(0, -60);
22
+ /**
23
+ * The frame of the "Text" animation that corresponds to when it reaches the center of the screen
24
+ * and stays put.
25
+ */
26
+ const TEXT_STAY_FRAME = 8;
27
+
28
+ /** The frame of the "Text" animation that corresponds to when it starts to move right. */
29
+ const TEXT_OUT_FRAME = 60;
30
+
31
+ /** This matches the offset that the vanilla game uses; determined via trial and error. */
32
+ const STREAK_SPRITE_TOP_OFFSET = Vector(0, 48.25);
33
+
34
+ /** This matches the offset that the vanilla game uses; determined via trial and error. */
35
+ const STREAK_SPRITE_BOTTOM_OFFSET = Vector(0, -48.25);
36
+
37
+ /**
38
+ * The offset from the bottom of the sprite that the rendered text should go; determined via trial
39
+ * and error.
40
+ */
41
+ const STREAK_TEXT_BOTTOM_Y_OFFSET = -9;
42
+
43
+ /**
44
+ * Corresponds to the vanilla value; determined via trial and error.
45
+ *
46
+ * 8 is too little and 9 has the vanilla text come out just slightly ahead, so we go with 9.
47
+ */
48
+ const NUM_RENDER_FRAMES_MAP_HELD_BEFORE_STREAK_TEXT = 11;
49
+
50
+ /** Corresponds to the vanilla value; determined through trial and error. */
51
+ const TEXT_PLAYBACK_SPEED = 0.5;
52
+
53
+ /** Taken from StageAPI. */
54
+ const TEXT_IN_ADJUSTMENTS = [-800, -639, -450, -250, -70, 10, 6, 3];
55
+
56
+ /** Taken from StageAPI. */
57
+ const TEXT_OUT_ADJUSTMENTS = [0, -5, -10, -15, -20, 144, 308, 472, 636, 800];
58
+
59
+ /** Taken from StageAPI. */
60
+ const TEXT_IN_SCALES = [
61
+ Vector(3, 0.2),
62
+ Vector(2.6, 0.36),
63
+ Vector(2.2, 0.52),
64
+ Vector(1.8, 0.68),
65
+ Vector(1.4, 0.84),
66
+ Vector(0.95, 1.05),
67
+ Vector(0.97, 1.03),
68
+ Vector(0.98, 1.02),
69
+ ];
70
+
71
+ /** Taken from StageAPI. */
72
+ const TEXT_OUT_SCALES = [
73
+ Vector(1, 1),
74
+ Vector(0.99, 1.03),
75
+ Vector(0.98, 1.05),
76
+ Vector(0.96, 1.08),
77
+ Vector(0.95, 1.1),
78
+ Vector(1.36, 0.92),
79
+ Vector(1.77, 0.74),
80
+ Vector(2.18, 0.56),
81
+ Vector(2.59, 0.38),
82
+ Vector(3, 0.2),
83
+ ];
84
+
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
+ topStreakSprite.Load("resources/gfx/ui/ui_streak.anm2", false);
92
+ topStreakSprite.PlaybackSpeed = TEXT_PLAYBACK_SPEED;
93
+
94
+ /**
95
+ * We do not actually need to render this sprite at all. It's only purpose is to be an invisible
96
+ * reference for when and where we need to render the stage's name. Thus, we specify "false" for
97
+ * "loadGraphics", but still manage playing its animations in the code below.
98
+ */
99
+ const bottomStreakSprite = Sprite();
100
+ bottomStreakSprite.Load("resources/gfx/ui/ui_streak.anm2", false);
101
+ bottomStreakSprite.PlaybackSpeed = TEXT_PLAYBACK_SPEED;
13
102
 
14
103
  // ModCallback.POST_RENDER (2)
15
- export function streakTextPostRender(customStage: CustomStage): void {
16
- if (!v.run.showingStreakText) {
104
+ export function streakTextPostRender(): void {
105
+ // The top streak only plays when the player arrives on the floor (or continues a game from the
106
+ // main menu.)
107
+ checkEndTopStreakText();
108
+
109
+ // The bottom streak only plays when the player holds down the map button.
110
+ trackMapInputPressed();
111
+ checkStartBottomStreakText();
112
+ checkEndBottomStreakText();
113
+ }
114
+
115
+ function checkEndTopStreakText() {
116
+ if (
117
+ v.run.topStreakTextStartedRenderFrame === null ||
118
+ !topStreakSprite.IsPlaying(UIStreakAnimation.TEXT_STAY)
119
+ ) {
17
120
  return;
18
121
  }
19
122
 
20
- const isPaused = game.IsPaused();
21
- if (isPaused) {
123
+ const renderFrameCount = Isaac.GetFrameCount();
124
+ const elapsedFrames =
125
+ renderFrameCount - v.run.topStreakTextStartedRenderFrame;
126
+ if (elapsedFrames >= 115) {
127
+ playTextOut(topStreakSprite);
128
+ }
129
+ }
130
+
131
+ function trackMapInputPressed() {
132
+ for (const controllerIndex of getEnumValues(ControllerIndex)) {
133
+ const gameFrameCount = game.GetFrameCount();
134
+ const oldPushedMapFrame =
135
+ v.run.controllerIndexPushingMapRenderFrame.get(controllerIndex);
136
+ const isPushingMap = Input.IsActionPressed(
137
+ ButtonAction.MAP,
138
+ controllerIndex,
139
+ );
140
+
141
+ if (isPushingMap) {
142
+ if (oldPushedMapFrame === undefined) {
143
+ v.run.controllerIndexPushingMapRenderFrame.set(
144
+ controllerIndex,
145
+ gameFrameCount,
146
+ );
147
+ }
148
+ } else {
149
+ v.run.controllerIndexPushingMapRenderFrame.delete(controllerIndex);
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * If the map input has been pressed down for long enough, play the animation where the level streak
156
+ * slides in from the left.
157
+ */
158
+ function checkStartBottomStreakText() {
159
+ if (bottomStreakSprite.IsPlaying()) {
22
160
  return;
23
161
  }
24
162
 
25
- const font = fonts.upheaval;
26
- const length = font.GetStringWidthUTF8(customStage.name);
27
- const bottomCenterPos = getScreenBottomCenterPos();
28
- const position = bottomCenterPos.add(STREAK_TEXT_BOTTOM_OFFSET);
29
- const adjustedX = position.X - length / 2;
30
- font.DrawString(customStage.name, adjustedX, position.Y, KColorDefault);
163
+ const pushedMapFrames = [
164
+ ...v.run.controllerIndexPushingMapRenderFrame.values(),
165
+ ];
166
+ if (pushedMapFrames.length === 0) {
167
+ return;
168
+ }
169
+
170
+ const earliestFrame = Math.min(...pushedMapFrames);
171
+ const gameFrameCount = game.GetFrameCount();
172
+ const elapsedFrames = gameFrameCount - earliestFrame;
173
+ if (elapsedFrames >= NUM_RENDER_FRAMES_MAP_HELD_BEFORE_STREAK_TEXT) {
174
+ bottomStreakSprite.Play(UIStreakAnimation.TEXT, true);
175
+ }
176
+ }
177
+
178
+ /**
179
+ * If the map input has been released, play the animation where the level streak slides out to the
180
+ * right.
181
+ */
182
+ function checkEndBottomStreakText() {
183
+ if (!bottomStreakSprite.IsPlaying(UIStreakAnimation.TEXT_STAY)) {
184
+ return;
185
+ }
186
+
187
+ const pushedMapFrames = [
188
+ ...v.run.controllerIndexPushingMapRenderFrame.values(),
189
+ ];
190
+ if (pushedMapFrames.length === 0) {
191
+ playTextOut(bottomStreakSprite);
192
+ }
31
193
  }
32
194
 
33
- // ModCallback.INPUT_ACTION (13)
34
- // TODO
195
+ // ModCallback.POST_GAME_STARTED (15)
196
+ export function streakTextPostGameStarted(): void {
197
+ topStreakSprite.Stop();
198
+ bottomStreakSprite.Stop();
199
+ }
35
200
 
36
201
  // ModCallback.GET_SHADER_PARAMS (22)
37
- export function streakTextGetShaderParams(shaderName: string): void {
202
+ export function streakTextGetShaderParams(
203
+ customStage: CustomStage,
204
+ shaderName: string,
205
+ ): void {
38
206
  if (shaderName !== EMPTY_SHADER_NAME) {
39
207
  return;
40
208
  }
41
209
 
42
- todo();
210
+ const topCenterPos = getScreenTopCenterPos();
211
+ const topStreakPosition = topCenterPos.add(STREAK_SPRITE_TOP_OFFSET);
212
+ renderSprite(customStage, topStreakSprite, topStreakPosition);
213
+
214
+ const bottomCenterPos = getScreenBottomCenterPos();
215
+ const bottomStreakPosition = bottomCenterPos.add(STREAK_SPRITE_BOTTOM_OFFSET);
216
+ renderSprite(customStage, bottomStreakSprite, bottomStreakPosition);
217
+ }
218
+
219
+ function renderSprite(
220
+ customStage: CustomStage,
221
+ sprite: Sprite,
222
+ position: Vector,
223
+ ) {
224
+ sprite.Update();
225
+ if (!sprite.IsPlaying()) {
226
+ return;
227
+ }
228
+
229
+ const animation = sprite.GetAnimation() as UIStreakAnimation;
230
+ const frame = sprite.GetFrame();
231
+ if (animation === UIStreakAnimation.TEXT && frame === TEXT_STAY_FRAME) {
232
+ sprite.Play(UIStreakAnimation.TEXT_STAY, true);
233
+ }
234
+
235
+ const isPaused = game.IsPaused();
236
+ if (isPaused) {
237
+ return;
238
+ }
239
+
240
+ const font = fonts.upheaval;
241
+ const { name } = customStage;
242
+ const length = font.GetStringWidthUTF8(name);
243
+ const centeredX = position.X - length / 2;
244
+
245
+ let adjustment = 0;
246
+ let scale = VectorOne;
247
+ switch (animation) {
248
+ case UIStreakAnimation.TEXT: {
249
+ if (frame < TEXT_STAY_FRAME) {
250
+ adjustment = TEXT_IN_ADJUSTMENTS[frame] ?? 0;
251
+ scale = TEXT_IN_SCALES[frame] ?? VectorOne;
252
+ } else {
253
+ const adjustedFrame = frame - TEXT_OUT_FRAME;
254
+ adjustment = TEXT_OUT_ADJUSTMENTS[adjustedFrame] ?? 0;
255
+ scale = TEXT_OUT_SCALES[adjustedFrame] ?? VectorOne;
256
+ }
257
+
258
+ break;
259
+ }
260
+
261
+ case UIStreakAnimation.TEXT_IN: {
262
+ adjustment = TEXT_IN_ADJUSTMENTS[frame] ?? 0;
263
+ scale = TEXT_IN_SCALES[frame] ?? VectorOne;
264
+ break;
265
+ }
266
+
267
+ case UIStreakAnimation.TEXT_OUT: {
268
+ adjustment = TEXT_OUT_ADJUSTMENTS[frame] ?? 0;
269
+ scale = TEXT_OUT_SCALES[frame] ?? VectorOne;
270
+ break;
271
+ }
272
+
273
+ default: {
274
+ break;
275
+ }
276
+ }
277
+
278
+ const adjustedX = centeredX + adjustment;
279
+ const adjustedY = position.Y + STREAK_TEXT_BOTTOM_Y_OFFSET;
280
+
281
+ sprite.RenderLayer(0, position);
282
+ font.DrawStringScaled(
283
+ name,
284
+ adjustedX,
285
+ adjustedY,
286
+ scale.X,
287
+ scale.Y,
288
+ KColorDefault,
289
+ );
290
+ }
291
+
292
+ function playTextOut(sprite: Sprite) {
293
+ sprite.Play(UIStreakAnimation.TEXT, true);
294
+ // We adjust by 2 to roughly align with the speed of the vanilla animation.
295
+ sprite.SetFrame(TEXT_OUT_FRAME - 2);
296
+ }
297
+
298
+ export function topStreakTextStart(): void {
299
+ const level = game.GetLevel();
300
+ const renderFrameCount = Isaac.GetFrameCount();
301
+
302
+ // Show the vanilla streak text, which will have a blank name because of the -1 floor.
303
+ level.ShowName(false);
304
+
305
+ // Initiate the animation for the custom text.
306
+ v.run.topStreakTextStartedRenderFrame = renderFrameCount;
307
+ topStreakSprite.Play(UIStreakAnimation.TEXT, true);
43
308
  }
@@ -1,10 +1,15 @@
1
+ import { ControllerIndex } from "isaac-typescript-definitions";
1
2
  import { CustomStage } from "../../interfaces/CustomStage";
2
3
 
3
4
  const v = {
4
5
  run: {
5
6
  currentCustomStage: null as CustomStage | null,
6
- showingStreakText: false,
7
7
  showingBossVersusScreen: false,
8
+
9
+ /** Values are the render frame that the controller first pressed the map button. */
10
+ controllerIndexPushingMapRenderFrame: new Map<ControllerIndex, int>(),
11
+
12
+ topStreakTextStartedRenderFrame: null as int | null,
8
13
  },
9
14
 
10
15
  room: {
@@ -91,15 +91,12 @@ versusScreenBackgroundSprite.Load("gfx/ui/boss/versusscreen.anm2", true);
91
91
  const versusScreenDirtSpotSprite = Sprite();
92
92
  versusScreenDirtSpotSprite.Load("gfx/ui/boss/versusscreen.anm2", true);
93
93
 
94
- export function playVersusScreenAnimation(
95
- customStage: CustomStage,
96
- force = false,
97
- ): void {
94
+ export function playVersusScreenAnimation(customStage: CustomStage): void {
98
95
  const room = game.GetRoom();
99
96
  const roomType = room.GetType();
100
97
  const hud = game.GetHUD();
101
98
 
102
- if (roomType !== RoomType.BOSS && !force) {
99
+ if (roomType !== RoomType.BOSS) {
103
100
  return;
104
101
  }
105
102
 
@@ -218,6 +218,7 @@ function initMap() {
218
218
  );
219
219
  extraConsoleCommandsFunctionMap.set("room", commands.roomCommand);
220
220
  extraConsoleCommandsFunctionMap.set("rottenHearts", commands.rottenHearts);
221
+ extraConsoleCommandsFunctionMap.set("runTests", commands.runTests);
221
222
  extraConsoleCommandsFunctionMap.set("s", commands.s);
222
223
  extraConsoleCommandsFunctionMap.set("sacrifice", commands.sacrifice);
223
224
  extraConsoleCommandsFunctionMap.set("secret", commands.secret);
@@ -53,6 +53,7 @@ import { getCardName } from "../../functions/cards";
53
53
  import { getCharacterName } from "../../functions/character";
54
54
  import { addCharge } from "../../functions/charge";
55
55
  import { isValidCollectibleType } from "../../functions/collectibles";
56
+ import { runDeepCopyTests } from "../../functions/deepCopyTests";
56
57
  import { getNPCs } from "../../functions/entitiesSpecific";
57
58
  import { getEnumValues } from "../../functions/enums";
58
59
  import { addFlag } from "../../functions/flag";
@@ -64,6 +65,7 @@ import {
64
65
  logSounds,
65
66
  } from "../../functions/log";
66
67
  import { getMapPartialMatch } from "../../functions/map";
68
+ import { runMergeTests } from "../../functions/mergeTests";
67
69
  import {
68
70
  spawnCard,
69
71
  spawnPill,
@@ -988,6 +990,18 @@ export function rottenHearts(params: string): void {
988
990
  addHeart(params, HealthType.ROTTEN);
989
991
  }
990
992
 
993
+ /**
994
+ * Run the suite of tests that prove that the "deepCopy" helper function and the "merge" function
995
+ * work properly. For more information, see the `runDeepCopyTests` and the `runMergeTests`
996
+ * functions.
997
+ *
998
+ * In general, running the tests is only useful if you are troubleshooting the save data manager.
999
+ */
1000
+ export function runTests(): void {
1001
+ runDeepCopyTests();
1002
+ runMergeTests();
1003
+ }
1004
+
991
1005
  /**
992
1006
  * Alias for the "stage" command.
993
1007
  *
@@ -132,12 +132,7 @@ export function saveDataManager(
132
132
 
133
133
  // Make a copy of the initial save data so that we can use it to restore the default values later
134
134
  // on.
135
- const saveDataTable = v as LuaTable<AnyNotNil, unknown>;
136
- const saveDataCopy = deepCopy(
137
- saveDataTable,
138
- SerializationType.NONE,
139
- key,
140
- ) as SaveData;
135
+ const saveDataCopy = deepCopy(v, SerializationType.NONE, key) as SaveData;
141
136
  saveDataDefaultsMap.set(key, saveDataCopy);
142
137
 
143
138
  // Store the conditional function for later, if present.
@@ -170,10 +165,10 @@ export function saveDataManagerSave(): void {
170
165
  }
171
166
 
172
167
  /** "g" stands for "globals". */
173
- declare let g: LuaTable<string, SaveData>; // eslint-disable-line @typescript-eslint/no-unused-vars
168
+ declare let g: LuaMap<string, SaveData>; // eslint-disable-line @typescript-eslint/no-unused-vars
174
169
 
175
170
  /** "gd" stands for "globals defaults". */
176
- declare let gd: LuaTable<string, SaveData>; // eslint-disable-line @typescript-eslint/no-unused-vars
171
+ declare let gd: LuaMap<string, SaveData>; // eslint-disable-line @typescript-eslint/no-unused-vars
177
172
 
178
173
  /**
179
174
  * - Sets the global variable of "g" equal to all of the save data variables for this mod.
@@ -13,7 +13,7 @@ const DEFAULT_MOD_DATA = "{}";
13
13
 
14
14
  export function loadFromDisk(
15
15
  mod: Mod,
16
- oldSaveData: LuaTable<string, SaveData>,
16
+ oldSaveData: LuaMap<string, SaveData>,
17
17
  ): void {
18
18
  if (!mod.HasData()) {
19
19
  // There is no "save#.dat" file for this save slot.
@@ -47,7 +47,6 @@ export function loadFromDisk(
47
47
 
48
48
  // Ignore elements that represent subscriptions that no longer exist in the current save data.
49
49
  const oldSaveDataForSubscriber = oldSaveData.get(key);
50
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
51
50
  if (oldSaveDataForSubscriber === undefined) {
52
51
  return;
53
52
  }
@@ -60,7 +59,7 @@ export function loadFromDisk(
60
59
  // We do not want to blow away the child tables of the existing map, because save data could
61
60
  // contain out-of-date fields. Instead, merge it one field at a time in a recursive way (and
62
61
  // convert Lua tables back to TypeScriptToLua Maps, if necessary).
63
- merge(oldSaveDataForSubscriber as LuaTable, value, key);
62
+ merge(oldSaveDataForSubscriber as LuaMap<AnyNotNil, unknown>, value, key);
64
63
  },
65
64
  SAVE_DATA_MANAGER_DEBUG,
66
65
  );
@@ -129,7 +129,6 @@ export function restoreDefaultSaveData(
129
129
 
130
130
  // Get the default values for this feature.
131
131
  const saveDataDefaults = saveDataDefaultsMap.get(subscriberName);
132
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
133
132
  if (saveDataDefaults === undefined) {
134
133
  logError(
135
134
  `Failed to find the default copy of the save data for subscriber: ${subscriberName}`,
@@ -151,13 +150,13 @@ export function restoreDefaultSaveData(
151
150
  childTableDefaults,
152
151
  SerializationType.NONE,
153
152
  `${subscriberName} --> ${saveDataKey}`,
154
- ) as LuaTable<AnyNotNil, unknown>;
153
+ ) as LuaMap<AnyNotNil, unknown>;
155
154
 
156
155
  // We do not want to blow away the existing child table because we don't want to break any
157
156
  // existing references. Instead, empty the table and copy all of the elements from the copy of the
158
157
  // defaults table.
159
158
  clearAndCopyAllElements(
160
- childTable as unknown as LuaTable,
159
+ childTable as unknown as LuaMap<AnyNotNil, unknown>,
161
160
  childTableDefaultsCopy,
162
161
  );
163
162
  }
@@ -167,8 +166,8 @@ export function restoreDefaultSaveData(
167
166
  * table to the old table.
168
167
  */
169
168
  function clearAndCopyAllElements(
170
- oldTable: LuaTable<AnyNotNil, unknown>,
171
- newTable: LuaTable<AnyNotNil, unknown>,
169
+ oldTable: LuaMap<AnyNotNil, unknown>,
170
+ newTable: LuaMap<AnyNotNil, unknown>,
172
171
  ) {
173
172
  clearTable(oldTable);
174
173
 
@@ -5,7 +5,7 @@ import { SaveData } from "../../interfaces/SaveData";
5
5
  * Maps for the master map so that we can access the variables via the in-game console when
6
6
  * debugging. (TSTL Maps don't expose the map keys as normal keys.)
7
7
  */
8
- export const saveDataMap = new LuaTable<string, SaveData>();
8
+ export const saveDataMap = new LuaMap<string, SaveData>();
9
9
 
10
- export const saveDataDefaultsMap = new LuaTable<string, SaveData>();
11
- export const saveDataConditionalFuncMap = new Map<string, () => boolean>();
10
+ export const saveDataDefaultsMap = new LuaMap<string, SaveData>();
11
+ export const saveDataConditionalFuncMap = new LuaMap<string, () => boolean>();
@@ -12,7 +12,7 @@ import { isDefaultMap, isTSTLMap, isTSTLSet } from "../../functions/tstlClass";
12
12
  import { isTable } from "../../functions/types";
13
13
  import { getTraversalDescription } from "../../functions/utils";
14
14
  import { SAVE_DATA_MANAGER_DEBUG } from "./saveDataManagerConstants";
15
- import { isSerializationBrand } from "./serializationBrand";
15
+ import { isSerializationBrand } from "./serializationBrands";
16
16
 
17
17
  /**
18
18
  * `merge` takes the values from a new table and recursively merges them into an old object (while
@@ -20,7 +20,7 @@ import { isSerializationBrand } from "./serializationBrand";
20
20
  *
21
21
  * It supports the following object types:
22
22
  *
23
- * - `LuaTable` / basic TSTL objects
23
+ * - Basic TSTL objects / tables
24
24
  * - TSTL `Map`
25
25
  * - TSTL `Set`
26
26
  * - TSTL classes
@@ -38,10 +38,10 @@ import { isSerializationBrand } from "./serializationBrand";
38
38
  */
39
39
  export function merge(
40
40
  oldObject:
41
- | LuaTable<AnyNotNil, unknown>
41
+ | LuaMap<AnyNotNil, unknown>
42
42
  | Map<AnyNotNil, unknown>
43
43
  | Set<AnyNotNil>,
44
- newTable: LuaTable<AnyNotNil, unknown>,
44
+ newTable: LuaMap<AnyNotNil, unknown>,
45
45
  traversalDescription: string,
46
46
  ): void {
47
47
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -73,8 +73,8 @@ export function merge(
73
73
  }
74
74
 
75
75
  function mergeArray(
76
- oldArray: LuaTable<AnyNotNil, unknown>,
77
- newArray: LuaTable<AnyNotNil, unknown>,
76
+ oldArray: LuaMap<AnyNotNil, unknown>,
77
+ newArray: LuaMap<AnyNotNil, unknown>,
78
78
  ) {
79
79
  // Assume that we should blow away all array values with whatever is present in the incoming
80
80
  // array.
@@ -90,7 +90,7 @@ function mergeArray(
90
90
 
91
91
  function mergeTSTLObject(
92
92
  oldObject: Map<AnyNotNil, unknown> | Set<AnyNotNil>,
93
- newTable: LuaTable<AnyNotNil, unknown>,
93
+ newTable: LuaMap<AnyNotNil, unknown>,
94
94
  traversalDescription: string,
95
95
  ) {
96
96
  // We blow away the old object and recursively copy over all of the incoming values.
@@ -140,8 +140,8 @@ function mergeTSTLObject(
140
140
  }
141
141
 
142
142
  function mergeTable(
143
- oldTable: LuaTable<AnyNotNil, unknown>,
144
- newTable: LuaTable<AnyNotNil, unknown>,
143
+ oldTable: LuaMap<AnyNotNil, unknown>,
144
+ newTable: LuaMap<AnyNotNil, unknown>,
145
145
  traversalDescription: string,
146
146
  ) {
147
147
  iterateTableInOrder(
@@ -170,12 +170,12 @@ function mergeTable(
170
170
  }
171
171
 
172
172
  if (isTable(value)) {
173
- let oldValue = oldTable.get(key) as LuaTable<AnyNotNil, unknown>;
173
+ let oldValue = oldTable.get(key) as LuaMap<AnyNotNil, unknown>;
174
174
  if (!isTable(oldValue)) {
175
175
  // The child table does not exist on the old table. However, we still need to copy over
176
176
  // the new table, because we need to handle data types like "Foo | null". Thus, set up a
177
177
  // blank sub-table on the old table, and continue to recursively merge..
178
- oldValue = new LuaTable();
178
+ oldValue = new LuaMap();
179
179
  oldTable.set(key, oldValue);
180
180
  }
181
181
 
@@ -11,8 +11,8 @@ import {
11
11
 
12
12
  export function saveToDisk(
13
13
  mod: Mod,
14
- saveDataMap: LuaTable<string, SaveData>,
15
- saveDataConditionalFuncMap: Map<string, () => boolean>,
14
+ saveDataMap: LuaMap<string, SaveData>,
15
+ saveDataConditionalFuncMap: LuaMap<string, () => boolean>,
16
16
  ): void {
17
17
  const allSaveData = getAllSaveDataToWriteToDisk(
18
18
  saveDataMap,
@@ -26,10 +26,10 @@ export function saveToDisk(
26
26
  }
27
27
 
28
28
  function getAllSaveDataToWriteToDisk(
29
- saveDataMap: LuaTable<string, SaveData>,
30
- saveDataConditionalFuncMap: Map<string, () => boolean>,
29
+ saveDataMap: LuaMap<string, SaveData>,
30
+ saveDataConditionalFuncMap: LuaMap<string, () => boolean>,
31
31
  ) {
32
- const allSaveData = new LuaTable<AnyNotNil, unknown>();
32
+ const allSaveData = new LuaMap<AnyNotNil, unknown>();
33
33
 
34
34
  iterateTableInOrder(
35
35
  saveDataMap,
@@ -60,7 +60,7 @@ function getAllSaveDataToWriteToDisk(
60
60
  // that is unnecessary. Make a copy of the data and recursively convert all TypeScriptToLua
61
61
  // Maps into Lua tables.
62
62
  const saveDataCopy = deepCopy(
63
- saveDataWithoutRoom as LuaTable,
63
+ saveDataWithoutRoom as LuaMap,
64
64
  SerializationType.SERIALIZE,
65
65
  subscriberName,
66
66
  );