isaacscript-common 6.10.1 → 6.11.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 (128) hide show
  1. package/dist/callbacks/postPlayerFatalDamage.lua +1 -1
  2. package/dist/callbacks/postPlayerInitFirst.d.ts +2 -0
  3. package/dist/callbacks/postPlayerInitFirst.d.ts.map +1 -0
  4. package/dist/callbacks/postPlayerInitFirst.lua +42 -0
  5. package/dist/callbacks/postPlayerInitLate.lua +5 -5
  6. package/dist/callbacks/postPlayerReorderedCallbacks.d.ts +2 -0
  7. package/dist/callbacks/postPlayerReorderedCallbacks.d.ts.map +1 -0
  8. package/dist/callbacks/{postPlayerReordered.lua → postPlayerReorderedCallbacks.lua} +11 -37
  9. package/dist/callbacks/subscriptions/{postPlayerInitReordered.d.ts → postPlayerInitFirst.d.ts} +2 -2
  10. package/dist/callbacks/subscriptions/postPlayerInitFirst.d.ts.map +1 -0
  11. package/dist/callbacks/subscriptions/{postPlayerInitReordered.lua → postPlayerInitFirst.lua} +3 -3
  12. package/dist/enums/ModCallbackCustom.d.ts +17 -15
  13. package/dist/enums/ModCallbackCustom.d.ts.map +1 -1
  14. package/dist/enums/ModCallbackCustom.lua +3 -3
  15. package/dist/enums/private/StageTravelState.d.ts +4 -0
  16. package/dist/enums/private/StageTravelState.d.ts.map +1 -0
  17. package/dist/enums/private/StageTravelState.lua +5 -0
  18. package/dist/features/customGridEntity.d.ts +2 -2
  19. package/dist/features/customGridEntity.d.ts.map +1 -1
  20. package/dist/features/customGridEntity.lua +14 -8
  21. package/dist/features/customStage/init.d.ts.map +1 -1
  22. package/dist/features/customStage/init.lua +8 -1
  23. package/dist/features/customStage/streakText.d.ts +6 -0
  24. package/dist/features/customStage/streakText.d.ts.map +1 -1
  25. package/dist/features/customStage/streakText.lua +16 -11
  26. package/dist/features/customStage/versusScreen.d.ts +6 -0
  27. package/dist/features/customStage/versusScreen.d.ts.map +1 -1
  28. package/dist/features/customStage/versusScreen.lua +10 -5
  29. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts +10 -0
  30. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts.map +1 -0
  31. package/dist/features/customTrapdoor/customTrapdoorConstants.lua +8 -0
  32. package/dist/features/customTrapdoor/exports.d.ts +37 -0
  33. package/dist/features/customTrapdoor/exports.d.ts.map +1 -0
  34. package/dist/features/customTrapdoor/exports.lua +127 -0
  35. package/dist/features/customTrapdoor/v.d.ts +4 -0
  36. package/dist/features/customTrapdoor/v.d.ts.map +1 -0
  37. package/dist/features/customTrapdoor/v.lua +15 -0
  38. package/dist/features/deployJSONRoom.d.ts.map +1 -1
  39. package/dist/features/deployJSONRoom.lua +1 -1
  40. package/dist/features/extraConsoleCommands/listCommands.lua +2 -2
  41. package/dist/functions/{character.d.ts → characters.d.ts} +3 -1
  42. package/dist/functions/characters.d.ts.map +1 -0
  43. package/dist/functions/{character.lua → characters.lua} +12 -0
  44. package/dist/functions/deepCopy.lua +20 -3
  45. package/dist/functions/deepCopyTests.d.ts.map +1 -1
  46. package/dist/functions/deepCopyTests.lua +55 -1
  47. package/dist/functions/jsonHelpers.d.ts +6 -0
  48. package/dist/functions/jsonHelpers.d.ts.map +1 -1
  49. package/dist/functions/jsonHelpers.lua +9 -3
  50. package/dist/functions/mergeTests.lua +0 -4
  51. package/dist/functions/playerIndex.d.ts +6 -2
  52. package/dist/functions/playerIndex.d.ts.map +1 -1
  53. package/dist/functions/playerIndex.lua +4 -2
  54. package/dist/functions/players.lua +4 -4
  55. package/dist/functions/revive.lua +2 -2
  56. package/dist/index.d.ts +3 -2
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.lua +10 -2
  59. package/dist/initCustomCallbacks.d.ts.map +1 -1
  60. package/dist/initCustomCallbacks.lua +5 -2
  61. package/dist/initFeatures.d.ts +1 -2
  62. package/dist/initFeatures.d.ts.map +1 -1
  63. package/dist/initFeatures.lua +7 -2
  64. package/dist/interfaces/AddCallbackParameterCustom.d.ts +2 -2
  65. package/dist/interfaces/AddCallbackParameterCustom.d.ts.map +1 -1
  66. package/dist/interfaces/CustomGridEntityData.d.ts +1 -1
  67. package/dist/interfaces/CustomGridEntityData.d.ts.map +1 -1
  68. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts +4 -0
  69. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts.map +1 -0
  70. package/dist/interfaces/private/CustomTrapdoorDescription.lua +2 -0
  71. package/dist/lib/jsonLua.lua +390 -0
  72. package/dist/objects/callbackRegisterFunctions.d.ts.map +1 -1
  73. package/dist/objects/callbackRegisterFunctions.lua +3 -3
  74. package/dist/objects/characterDamageMultipliers.d.ts +6 -0
  75. package/dist/objects/characterDamageMultipliers.d.ts.map +1 -0
  76. package/dist/objects/characterDamageMultipliers.lua +49 -0
  77. package/dist/upgradeMod.d.ts.map +1 -1
  78. package/dist/upgradeMod.lua +2 -4
  79. package/package.json +2 -2
  80. package/src/callbacks/customRevive.ts +3 -3
  81. package/src/callbacks/itemPickup.ts +3 -3
  82. package/src/callbacks/postAmbush.ts +3 -3
  83. package/src/callbacks/postEsauJr.ts +3 -3
  84. package/src/callbacks/postFlip.ts +3 -3
  85. package/src/callbacks/postGridEntity.ts +5 -5
  86. package/src/callbacks/postPlayerCollectible.ts +2 -2
  87. package/src/callbacks/postPlayerFatalDamage.ts +5 -0
  88. package/src/callbacks/postPlayerInitFirst.ts +57 -0
  89. package/src/callbacks/postPlayerInitLate.ts +9 -5
  90. package/src/callbacks/{postPlayerReordered.ts → postPlayerReorderedCallbacks.ts} +9 -29
  91. package/src/callbacks/postSlotInitUpdate.ts +5 -2
  92. package/src/callbacks/postSlotRender.ts +2 -2
  93. package/src/callbacks/reorderedCallbacks.ts +1 -1
  94. package/src/callbacks/subscriptions/{postPlayerInitReordered.ts → postPlayerInitFirst.ts} +6 -6
  95. package/src/enums/ModCallbackCustom.ts +17 -15
  96. package/src/enums/private/StageTravelState.ts +4 -0
  97. package/src/features/customGridEntity.ts +29 -6
  98. package/src/features/customStage/init.ts +10 -1
  99. package/src/features/customStage/streakText.ts +13 -4
  100. package/src/features/customStage/versusScreen.ts +20 -12
  101. package/src/features/customTrapdoor/customTrapdoorConstants.ts +13 -0
  102. package/src/features/customTrapdoor/exports.ts +168 -0
  103. package/src/features/customTrapdoor/v.ts +20 -0
  104. package/src/features/deployJSONRoom.ts +5 -0
  105. package/src/features/extraConsoleCommands/listCommands.ts +1 -1
  106. package/src/functions/{character.ts → characters.ts} +13 -0
  107. package/src/functions/deepCopy.ts +18 -1
  108. package/src/functions/deepCopyTests.ts +112 -0
  109. package/src/functions/jsonHelpers.ts +9 -3
  110. package/src/functions/mergeTests.ts +0 -8
  111. package/src/functions/playerIndex.ts +6 -2
  112. package/src/functions/players.ts +1 -1
  113. package/src/functions/revive.ts +1 -1
  114. package/src/index.ts +6 -2
  115. package/src/initCustomCallbacks.ts +3 -1
  116. package/src/initFeatures.ts +7 -2
  117. package/src/interfaces/AddCallbackParameterCustom.ts +2 -2
  118. package/src/interfaces/CustomGridEntityData.ts +1 -1
  119. package/src/interfaces/private/CustomTrapdoorDescription.ts +3 -0
  120. package/src/lib/jsonLua.d.ts +10 -0
  121. package/src/lib/jsonLua.lua +390 -0
  122. package/src/objects/callbackRegisterFunctions.ts +2 -3
  123. package/src/objects/characterDamageMultipliers.ts +49 -0
  124. package/src/upgradeMod.ts +2 -3
  125. package/dist/callbacks/postPlayerReordered.d.ts +0 -2
  126. package/dist/callbacks/postPlayerReordered.d.ts.map +0 -1
  127. package/dist/callbacks/subscriptions/postPlayerInitReordered.d.ts.map +0 -1
  128. package/dist/functions/character.d.ts.map +0 -1
@@ -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,13 @@
1
+ import { GridEntityType } from "isaac-typescript-definitions";
2
+
3
+ export const CUSTOM_TRAPDOOR_FEATURE_NAME = "customTrapdoor";
4
+
5
+ /** This also applies to crawl spaces. The value was determined through trial and error. */
6
+ export const TRAPDOOR_OPEN_DISTANCE = 60;
7
+
8
+ export const TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS = TRAPDOOR_OPEN_DISTANCE * 2.5;
9
+ export const TRAPDOOR_BOSS_REACTION_FRAMES = 30;
10
+
11
+ export const GridEntityTypeCustom = {
12
+ TRAPDOOR_CUSTOM: 1000 as GridEntityType,
13
+ } as const;
@@ -0,0 +1,168 @@
1
+ import {
2
+ GridCollisionClass,
3
+ LevelStage,
4
+ RoomType,
5
+ StageType,
6
+ } from "isaac-typescript-definitions";
7
+ import { game } from "../../cachedClasses";
8
+ import { errorIfFeaturesNotInitialized } from "../../featuresInitialized";
9
+ import { getNextStage, getNextStageType } from "../../functions/nextStage";
10
+ import { anyPlayerCloserThan } from "../../functions/positionVelocity";
11
+ import { spawnCustomGridEntity } from "../customGridEntity";
12
+ import { getRoomClearGameFrame } from "../roomClearFrame";
13
+ import {
14
+ CUSTOM_TRAPDOOR_FEATURE_NAME,
15
+ GridEntityTypeCustom,
16
+ TRAPDOOR_BOSS_REACTION_FRAMES,
17
+ TRAPDOOR_OPEN_DISTANCE,
18
+ TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS,
19
+ } from "./customTrapdoorConstants";
20
+ import { getCustomTrapdoorDescription } from "./v";
21
+
22
+ /**
23
+ * Helper function to spawn a trapdoor grid entity that will have one or more of the following
24
+ * attributes:
25
+ *
26
+ * - custom destination (or custom logic for after the player enters)
27
+ * - custom graphics
28
+ * - custom logic for opening/closing
29
+ * - TODO: animation
30
+ *
31
+ * You can use this function to take the player to your custom stage.
32
+ *
33
+ * Under the hood, the custom trapdoor is represented by a decoration grid entity and is manually
34
+ * respawned every time the player re-enters the room.
35
+ *
36
+ * @param gridIndexOrPosition The location in the room to spawn the trapdoor.
37
+ * @param _destination Used to specify where the player will go after jumping into the trapdoor. Can
38
+ * either be a tuple containing the stage and stage type, a string containing
39
+ * the name of a custom stage, or undefined. If undefined, nothing will happen
40
+ * after the player jumps in the trapdoor. (Use undefined to perform some custom
41
+ * behavior and/or handle the traveling part yourself.) You can also specify a
42
+ * function that returns one of these things. By default, the destination will
43
+ * be set to the next floor like that of a vanilla trapdoor.
44
+ * @param anm2Path Optional. The path to the anm2 file to use. By default, the vanilla trapdoor anm2
45
+ * of "gfx/grid/door_11_trapdoor.anm2" will be used.
46
+ * @param _shouldOpenFunc Optional. If the trapdoor is currently closed, this function will run on
47
+ * every frame to determine if it should open. By default, a function that
48
+ * emulates a vanilla trapdoor will be used.
49
+ * @param _shouldCloseFunc Optional. If the trapdoor is currently open, this function will run on
50
+ * every frame to determine if it should close. By default, a function that
51
+ * emulates a vanilla trapdoor will be used.
52
+ * @param _spawnOpen Optional. Whether or not to spawn the trapdoor in an open state. Can either be
53
+ * a boolean or a function returning a boolean. By default, a function that
54
+ * emulates a vanilla trapdoor will be used.
55
+ */
56
+ export function spawnCustomTrapdoor(
57
+ gridIndexOrPosition: int | Vector,
58
+ _destination:
59
+ | [stage: LevelStage, stageType: StageType]
60
+ | string
61
+ | ((
62
+ gridEntity: GridEntity,
63
+ ) =>
64
+ | [stage: LevelStage, stageType: StageType]
65
+ | string
66
+ | undefined) = defaultDestinationFunc,
67
+ anm2Path = "gfx/grid/door_11_trapdoor.anm2",
68
+ _shouldOpenFunc: (gridEntity: GridEntity) => boolean = defaultShouldOpenFunc,
69
+ _shouldCloseFunc: (
70
+ gridEntity: GridEntity,
71
+ ) => boolean = defaultShouldCloseFunc,
72
+ _spawnOpen:
73
+ | boolean
74
+ | ((gridEntity: GridEntity) => boolean) = defaultShouldSpawnOpenFunc,
75
+ ): GridEntity {
76
+ errorIfFeaturesNotInitialized(CUSTOM_TRAPDOOR_FEATURE_NAME);
77
+
78
+ // TODO
79
+ return spawnCustomGridEntity(
80
+ GridEntityTypeCustom.TRAPDOOR_CUSTOM,
81
+ gridIndexOrPosition,
82
+ anm2Path,
83
+ "Closed",
84
+ GridCollisionClass.NONE,
85
+ );
86
+ }
87
+
88
+ function defaultDestinationFunc(): [stage: LevelStage, stageType: StageType] {
89
+ const nextStage = getNextStage();
90
+ const nextStageType = getNextStageType();
91
+
92
+ return [nextStage, nextStageType];
93
+ }
94
+
95
+ function defaultShouldOpenFunc(gridEntity: GridEntity): boolean {
96
+ const trapdoorDescription = getCustomTrapdoorDescription(gridEntity);
97
+ if (trapdoorDescription === undefined) {
98
+ return false;
99
+ }
100
+
101
+ const room = game.GetRoom();
102
+ const roomClear = room.IsClear();
103
+
104
+ return (
105
+ !anyPlayerCloserThan(gridEntity.Position, TRAPDOOR_OPEN_DISTANCE) &&
106
+ !isPlayerCloseAfterBoss(gridEntity.Position) &&
107
+ !shouldBeClosedFromStartingInRoomWithEnemies(
108
+ trapdoorDescription.firstSpawn,
109
+ roomClear,
110
+ )
111
+ );
112
+ }
113
+
114
+ function isPlayerCloseAfterBoss(position: Vector) {
115
+ const gameFrameCount = game.GetFrameCount();
116
+ const room = game.GetRoom();
117
+ const roomType = room.GetType();
118
+ const roomClearGameFrame = getRoomClearGameFrame();
119
+
120
+ // In order to prevent a player from accidentally entering a freshly-spawned trapdoor after
121
+ // killing the boss of the floor, we use a wider open distance for a short amount of frames.
122
+ if (
123
+ roomType !== RoomType.BOSS ||
124
+ roomClearGameFrame === undefined ||
125
+ gameFrameCount >= roomClearGameFrame + TRAPDOOR_BOSS_REACTION_FRAMES
126
+ ) {
127
+ return false;
128
+ }
129
+
130
+ return anyPlayerCloserThan(position, TRAPDOOR_OPEN_DISTANCE_AFTER_BOSS);
131
+ }
132
+
133
+ function shouldBeClosedFromStartingInRoomWithEnemies(
134
+ firstSpawn: boolean,
135
+ roomClear: boolean,
136
+ ) {
137
+ return firstSpawn && !roomClear;
138
+ }
139
+
140
+ /** By default, trapdoors will never close if they are already open. */
141
+ function defaultShouldCloseFunc(): boolean {
142
+ return false;
143
+ }
144
+
145
+ function defaultShouldSpawnOpenFunc(gridEntity: GridEntity): boolean {
146
+ const room = game.GetRoom();
147
+ const roomFrameCount = room.GetFrameCount();
148
+ const roomClear = room.IsClear();
149
+
150
+ // Trapdoors created after a room has already initialized should spawn closed by default:
151
+ // - Trapdoors created after bosses should spawn closed so that players do not accidentally jump
152
+ // into them.
153
+ // - Trapdoors created by We Need to Go Deeper should spawn closed because the player will be
154
+ // standing on top of them.
155
+ if (roomFrameCount > 0) {
156
+ return false;
157
+ }
158
+
159
+ // If we just entered a new room with enemies in it, spawn the trapdoor closed so that the player
160
+ // has to defeat the enemies first before using the trapdoor.
161
+ if (!roomClear) {
162
+ return false;
163
+ }
164
+
165
+ // If we just entered a new room that is already cleared, spawn the trapdoor closed if we are
166
+ // standing close to it, and open otherwise.
167
+ return defaultShouldOpenFunc(gridEntity);
168
+ }
@@ -0,0 +1,20 @@
1
+ import { StageTravelState } from "../../enums/private/StageTravelState";
2
+ import { CustomTrapdoorDescription } from "../../interfaces/private/CustomTrapdoorDescription";
3
+
4
+ const v = {
5
+ run: {
6
+ state: StageTravelState.NONE,
7
+ },
8
+
9
+ room: {
10
+ /** Indexed by grid index. */
11
+ trapdoors: new Map<int, CustomTrapdoorDescription>(),
12
+ },
13
+ };
14
+
15
+ export function getCustomTrapdoorDescription(
16
+ gridEntity: GridEntity,
17
+ ): CustomTrapdoorDescription | undefined {
18
+ const gridIndex = gridEntity.GetGridIndex();
19
+ return v.room.trapdoors.get(gridIndex);
20
+ }
@@ -4,6 +4,7 @@
4
4
  // it from scratch based on the JSON data.
5
5
 
6
6
  import {
7
+ ActiveSlot,
7
8
  CollectibleType,
8
9
  EffectVariant,
9
10
  EntityCollisionClass,
@@ -16,6 +17,7 @@ import {
16
17
  PickupVariant,
17
18
  PitfallVariant,
18
19
  RoomType,
20
+ UseFlag,
19
21
  } from "isaac-typescript-definitions";
20
22
  import { game } from "../cachedClasses";
21
23
  import { DefaultMap } from "../classes/DefaultMap";
@@ -113,6 +115,9 @@ function preUseItemWeNeedToGoDeeper(
113
115
  _collectibleType: CollectibleType,
114
116
  _rng: RNG,
115
117
  player: EntityPlayer,
118
+ _useFlags: BitFlags<UseFlag>,
119
+ _activeSlot: ActiveSlot,
120
+ _customVarData: int,
116
121
  ): boolean | undefined {
117
122
  if (v.room.manuallyUsingShovel) {
118
123
  return undefined;
@@ -50,7 +50,7 @@ import {
50
50
  } from "../../constantsFirstLast";
51
51
  import { HealthType } from "../../enums/HealthType";
52
52
  import { getCardName } from "../../functions/cards";
53
- import { getCharacterName } from "../../functions/character";
53
+ import { getCharacterName } from "../../functions/characters";
54
54
  import { addCharge } from "../../functions/charge";
55
55
  import { isValidCollectibleType } from "../../functions/collectibles";
56
56
  import { runDeepCopyTests } from "../../functions/deepCopyTests";
@@ -1,5 +1,6 @@
1
1
  import { PlayerType } from "isaac-typescript-definitions";
2
2
  import { LAST_VANILLA_CHARACTER } from "../constantsFirstLast";
3
+ import { CHARACTER_DAMAGE_MULTIPLIERS } from "../objects/characterDamageMultipliers";
3
4
  import { CHARACTER_NAMES } from "../objects/characterNames";
4
5
  import { CHARACTERS_THAT_START_WITH_AN_ACTIVE_ITEM_SET } from "../sets/charactersThatStartWithAnActiveItemSet";
5
6
  import { CHARACTERS_WITH_BLACK_HEART_FROM_ETERNAL_HEART_SET } from "../sets/charactersWithBlackHeartFromEternalHeartSet";
@@ -56,6 +57,18 @@ export function characterStartsWithActiveItem(character: PlayerType): boolean {
56
57
  return CHARACTERS_THAT_START_WITH_AN_ACTIVE_ITEM_SET.has(character);
57
58
  }
58
59
 
60
+ /** Helper function to get the numerical damage multiplier for a character. */
61
+ export function getCharacterDamageMultiplier(
62
+ character: PlayerType,
63
+ hasWhoreOfBabylon = false,
64
+ ): float {
65
+ if (character === PlayerType.EVE && hasWhoreOfBabylon) {
66
+ return 1.0;
67
+ }
68
+
69
+ return CHARACTER_DAMAGE_MULTIPLIERS[character];
70
+ }
71
+
59
72
  /**
60
73
  * - Most characters have a 56 frame death animation (i.e. the "Death" animation).
61
74
  * - The Lost and Tainted Lost have a 38 frame death animation (i.e. the "LostDeath" animation).
@@ -568,6 +568,14 @@ function getCopiedEntries(
568
568
  entries.sort(twoDimensionalSort);
569
569
  }
570
570
 
571
+ // During serialization, we brand some Lua tables with a special identifier to signify that it has
572
+ // keys that should be deserialized to numbers.
573
+ const convertStringKeysToNumbers =
574
+ serializationType === SerializationType.DESERIALIZE &&
575
+ entries.some(
576
+ ([key]) => key === (SerializationBrand.OBJECT_WITH_NUMBER_KEYS as string),
577
+ );
578
+
571
579
  const hasNumberKeys = entries.some(([key]) => isNumber(key));
572
580
  const convertNumberKeysToStrings =
573
581
  serializationType === SerializationType.SERIALIZE && hasNumberKeys;
@@ -589,7 +597,16 @@ function getCopiedEntries(
589
597
  insideMap,
590
598
  );
591
599
 
592
- const keyToUse = convertNumberKeysToStrings ? tostring(key) : key;
600
+ let keyToUse = key;
601
+ if (convertStringKeysToNumbers) {
602
+ const numberKey = tonumber(key);
603
+ if (numberKey !== undefined) {
604
+ keyToUse = numberKey;
605
+ }
606
+ }
607
+ if (convertNumberKeysToStrings) {
608
+ keyToUse = tostring(key);
609
+ }
593
610
  copiedEntries.push([keyToUse, newValue]);
594
611
  }
595
612
 
@@ -32,6 +32,12 @@ export function runDeepCopyTests(): void {
32
32
  copiedDefaultMapHasChildDefaultMap();
33
33
  copiedDefaultMapHasBrand();
34
34
 
35
+ copiedSerializedMapHasStringKey();
36
+ copiedSerializedMapHasNumberKey();
37
+
38
+ copiedSerializedDefaultMapHasStringKey();
39
+ copiedSerializedDefaultMapHasNumberKey();
40
+
35
41
  log("All deep copy tests passed!");
36
42
  }
37
43
 
@@ -457,3 +463,109 @@ function copiedDefaultMapHasBrand() {
457
463
  );
458
464
  }
459
465
  }
466
+
467
+ function copiedSerializedMapHasStringKey() {
468
+ const mapKey = "123";
469
+ const mapValue = 456;
470
+ const oldMap = new Map<string, number>();
471
+ oldMap.set(mapKey, mapValue);
472
+
473
+ const serializedOldMap = deepCopy(
474
+ oldMap,
475
+ SerializationType.SERIALIZE,
476
+ "copiedSerializedMapHasStringKey-serialize",
477
+ );
478
+
479
+ const newTable = deepCopy(
480
+ serializedOldMap,
481
+ SerializationType.DESERIALIZE,
482
+ "copiedSerializedMapHasStringKey-deserialize",
483
+ );
484
+
485
+ const newMap = newTable as Map<string, number>;
486
+ if (!newMap.has(mapKey)) {
487
+ const keyType = type(mapKey);
488
+ error(
489
+ `The copied Map did not have a key of: ${mapKey} with type ${keyType}`,
490
+ );
491
+ }
492
+ }
493
+
494
+ function copiedSerializedMapHasNumberKey() {
495
+ const mapKey = 123;
496
+ const mapValue = 456;
497
+ const oldMap = new Map<number, number>();
498
+ oldMap.set(mapKey, mapValue);
499
+
500
+ const serializedOldMap = deepCopy(
501
+ oldMap,
502
+ SerializationType.SERIALIZE,
503
+ "copiedSerializedMapHasNumberKey-serialize",
504
+ );
505
+
506
+ const newTable = deepCopy(
507
+ serializedOldMap,
508
+ SerializationType.DESERIALIZE,
509
+ "copiedSerializedMapHasNumberKey-deserialize",
510
+ );
511
+
512
+ const newMap = newTable as Map<number, number>;
513
+ if (!newMap.has(mapKey)) {
514
+ const keyType = type(mapKey);
515
+ error(
516
+ `The copied Map did not have a key of: ${mapKey} with type ${keyType}`,
517
+ );
518
+ }
519
+ }
520
+
521
+ function copiedSerializedDefaultMapHasStringKey() {
522
+ const mapKey = "123";
523
+ const oldDefaultMap = new DefaultMap<string, number>(456);
524
+ oldDefaultMap.getAndSetDefault(mapKey);
525
+
526
+ const serializedOldDefaultMap = deepCopy(
527
+ oldDefaultMap,
528
+ SerializationType.SERIALIZE,
529
+ "copiedSerializedDefaultMapHasStringKey-serialize",
530
+ );
531
+
532
+ const newTable = deepCopy(
533
+ serializedOldDefaultMap,
534
+ SerializationType.DESERIALIZE,
535
+ "copiedSerializedDefaultMapHasStringKey-deserialize",
536
+ );
537
+
538
+ const newDefaultMap = newTable as DefaultMap<string, number>;
539
+ if (!newDefaultMap.has(mapKey)) {
540
+ const keyType = type(mapKey);
541
+ error(
542
+ `The copied DefaultMap did not have a key of "${mapKey}" with type: ${keyType}`,
543
+ );
544
+ }
545
+ }
546
+
547
+ function copiedSerializedDefaultMapHasNumberKey() {
548
+ const mapKey = 123;
549
+ const oldDefaultMap = new DefaultMap<number, number>(456);
550
+ oldDefaultMap.getAndSetDefault(mapKey);
551
+
552
+ const serializedOldDefaultMap = deepCopy(
553
+ oldDefaultMap,
554
+ SerializationType.SERIALIZE,
555
+ "copiedSerializedDefaultMapHasNumberKey-serialize",
556
+ );
557
+
558
+ const newTable = deepCopy(
559
+ serializedOldDefaultMap,
560
+ SerializationType.DESERIALIZE,
561
+ "copiedSerializedDefaultMapHasNumberKey-deserialize",
562
+ );
563
+
564
+ const newDefaultMap = newTable as DefaultMap<number, number>;
565
+ if (!newDefaultMap.has(mapKey)) {
566
+ const keyType = type(mapKey);
567
+ error(
568
+ `The copied DefaultMap did not have a key of: ${mapKey} with type ${keyType}`,
569
+ );
570
+ }
571
+ }
@@ -1,12 +1,12 @@
1
- import * as json from "json";
1
+ import * as jsonLua from "../lib/jsonLua";
2
2
  import { logError } from "./log";
3
3
 
4
4
  function tryDecode(this: void, jsonString: string) {
5
- return json.decode(jsonString) as LuaMap;
5
+ return jsonLua.decode(jsonString) as LuaMap;
6
6
  }
7
7
 
8
8
  function tryEncode(this: void, luaTable: unknown) {
9
- return json.encode(luaTable);
9
+ return jsonLua.encode(luaTable);
10
10
  }
11
11
 
12
12
  /**
@@ -16,6 +16,9 @@ function tryEncode(this: void, luaTable: unknown) {
16
16
  * fails, it will return a blank Lua table instead of throwing an error. (This allows execution to
17
17
  * continue in cases where users have no current save data or have manually removed their existing
18
18
  * save data.)
19
+ *
20
+ * Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
21
+ * vanilla JSON parser.
19
22
  */
20
23
  export function jsonDecode(jsonString: string): LuaMap<AnyNotNil, unknown> {
21
24
  const [ok, luaTableOrErrMsg] = pcall(tryDecode, jsonString);
@@ -34,6 +37,9 @@ export function jsonDecode(jsonString: string): LuaMap<AnyNotNil, unknown> {
34
37
  * In most cases, this function will be used for writing data to a "save#.dat" file. If encoding
35
38
  * fails, it will throw an error to prevent writing a blank string or corrupted data to a user's
36
39
  * "save#.dat" file.
40
+ *
41
+ * Under the hood, this uses a custom JSON parser that was measured to be 11.8 times faster than the
42
+ * vanilla JSON parser.
37
43
  */
38
44
  export function jsonEncode(luaTable: unknown): string {
39
45
  const [ok, jsonStringOrErrMsg] = pcall(tryEncode, luaTable);
@@ -266,8 +266,6 @@ function oldTableHasFilledDefaultMap() {
266
266
  }
267
267
 
268
268
  function oldTableHasVector() {
269
- log("Starting test: oldTableHasVector");
270
-
271
269
  interface Foo {
272
270
  bar: Vector;
273
271
  }
@@ -307,8 +305,6 @@ function oldTableHasVector() {
307
305
  }
308
306
 
309
307
  function oldTableHasVectorSerialized() {
310
- log("Starting test: oldTableHasVectorSerialized");
311
-
312
308
  interface Foo {
313
309
  bar: Vector;
314
310
  }
@@ -355,8 +351,6 @@ function oldTableHasVectorSerialized() {
355
351
  }
356
352
 
357
353
  function oldTableHasRNG() {
358
- log("Starting test: oldTableHasRNG");
359
-
360
354
  interface Foo {
361
355
  bar: RNG;
362
356
  }
@@ -392,8 +386,6 @@ function oldTableHasRNG() {
392
386
  }
393
387
 
394
388
  function oldTableHasRNGSerialized() {
395
- log("Starting test: oldTableHasRNGSerialized");
396
-
397
389
  interface Foo {
398
390
  bar: RNG;
399
391
  }
@@ -28,6 +28,10 @@ export function getAllPlayers(): EntityPlayer[] {
28
28
  return players;
29
29
  }
30
30
 
31
+ /**
32
+ * Helper function to get the corresponding `EntityPlayer` object that corresponds to a
33
+ * `PlayerIndex`.
34
+ */
31
35
  export function getPlayerFromIndex(
32
36
  playerIndex: PlayerIndex,
33
37
  ): EntityPlayer | undefined {
@@ -194,8 +198,8 @@ export function getSubPlayerParent(
194
198
  }
195
199
 
196
200
  /**
197
- * Some players are "child" players, meaning that they have a non-undefined Parent property. (For
198
- * example, the Strawman Keeper.)
201
+ * Helper function to detect if a particular player is a "child" player, meaning that they have a
202
+ * non-undefined `EntityPlayer.Parent` property. (For example, the Strawman Keeper.)
199
203
  */
200
204
  export function isChildPlayer(player: EntityPlayer): boolean {
201
205
  return player.Parent !== undefined;
@@ -17,7 +17,7 @@ import {
17
17
  getCharacterMaxHeartContainers,
18
18
  getCharacterName,
19
19
  isVanillaCharacter,
20
- } from "./character";
20
+ } from "./characters";
21
21
  import { getCollectibleMaxCharges } from "./collectibles";
22
22
  import { getCollectibleArray } from "./collectibleSet";
23
23
  import { getEnumValues } from "./enums";
@@ -10,7 +10,7 @@ import {
10
10
  MAX_TAINTED_SAMSON_BERSERK_CHARGE,
11
11
  TAINTED_SAMSON_BERSERK_CHARGE_FROM_TAKING_DAMAGE,
12
12
  } from "../constants";
13
- import { getCharacterDeathAnimationName } from "./character";
13
+ import { getCharacterDeathAnimationName } from "./characters";
14
14
  import {
15
15
  getPlayerMaxHeartContainers,
16
16
  getPlayerNumHitsRemaining,
package/src/index.ts CHANGED
@@ -22,8 +22,12 @@ export {
22
22
  } from "./features/characterHealthConversion";
23
23
  export { registerCharacterStats } from "./features/characterStats";
24
24
  export { getCollectibleItemPoolType } from "./features/collectibleItemPoolType";
25
- export { removeCustomGrid, spawnCustomGrid } from "./features/customGridEntity";
25
+ export {
26
+ removeCustomGrid,
27
+ spawnCustomGridEntity as spawnCustomGrid,
28
+ } from "./features/customGridEntity";
26
29
  export * from "./features/customStage/exports";
30
+ export * from "./features/customTrapdoor/exports";
27
31
  export * from "./features/debugDisplay/exports";
28
32
  export {
29
33
  deployJSONRoom,
@@ -82,7 +86,7 @@ export * from "./functions/bosses";
82
86
  export * from "./functions/cacheFlag";
83
87
  export * from "./functions/cards";
84
88
  export * from "./functions/challenges";
85
- export * from "./functions/character";
89
+ export * from "./functions/characters";
86
90
  export * from "./functions/charge";
87
91
  export * from "./functions/chargeBar";
88
92
  export * from "./functions/collectibleCacheFlag";
@@ -37,8 +37,9 @@ import { postPlayerChangeHealthInit } from "./callbacks/postPlayerChangeHealth";
37
37
  import { postPlayerChangeTypeInit } from "./callbacks/postPlayerChangeType";
38
38
  import { postPlayerCollectibleCallbacksInit } from "./callbacks/postPlayerCollectible";
39
39
  import { postPlayerFatalDamageInit } from "./callbacks/postPlayerFatalDamage";
40
+ import { postPlayerInitFirstInit } from "./callbacks/postPlayerInitFirst";
40
41
  import { postPlayerInitLateInit } from "./callbacks/postPlayerInitLate";
41
- import { postPlayerReorderedCallbacksInit } from "./callbacks/postPlayerReordered";
42
+ import { postPlayerReorderedCallbacksInit } from "./callbacks/postPlayerReorderedCallbacks";
42
43
  import { postPoopRenderInit } from "./callbacks/postPoopRender";
43
44
  import { postPoopUpdateInit } from "./callbacks/postPoopUpdate";
44
45
  import { postPressurePlateRenderInit } from "./callbacks/postPressurePlateRender";
@@ -105,6 +106,7 @@ export function initCustomCallbacks(mod: ModUpgraded): void {
105
106
  postPlayerChangeTypeInit(mod);
106
107
  postPlayerCollectibleCallbacksInit(mod);
107
108
  postPlayerFatalDamageInit(mod);
109
+ postPlayerInitFirstInit(mod);
108
110
  postPlayerInitLateInit(mod);
109
111
  postPlayerReorderedCallbacksInit(mod);
110
112
  postPoopRenderInit(mod);
@@ -23,7 +23,12 @@ import { sirenHelpersInit } from "./features/sirenHelpers";
23
23
  import { stageHistoryInit } from "./features/stageHistory";
24
24
  import { taintedLazarusPlayersInit } from "./features/taintedLazarusPlayers";
25
25
 
26
- export function initFeaturesMajor(mod: ModUpgraded): void {
26
+ export function initFeatures(mod: ModUpgraded): void {
27
+ initFeaturesMajor(mod);
28
+ initFeaturesMinor(mod);
29
+ }
30
+
31
+ function initFeaturesMajor(mod: ModUpgraded) {
27
32
  customStageInit(mod);
28
33
  deployJSONRoomInit(mod);
29
34
  runInNFramesInit(mod);
@@ -32,7 +37,7 @@ export function initFeaturesMajor(mod: ModUpgraded): void {
32
37
  customGridEntityInit(mod);
33
38
  }
34
39
 
35
- export function initFeaturesMinor(mod: ModUpgraded): void {
40
+ function initFeaturesMinor(mod: ModUpgraded) {
36
41
  disableAllSoundInit(mod);
37
42
  disableInputsInit(mod);
38
43
  fadeInRemoverInit(mod);
@@ -50,8 +50,8 @@ import { PostPlayerChangeTypeRegisterParameters } from "../callbacks/subscriptio
50
50
  import { PostPlayerCollectibleAddedRegisterParameters } from "../callbacks/subscriptions/postPlayerCollectibleAdded";
51
51
  import { PostPlayerCollectibleRemovedRegisterParameters } from "../callbacks/subscriptions/postPlayerCollectibleRemoved";
52
52
  import { PostPlayerFatalDamageRegisterParameters } from "../callbacks/subscriptions/postPlayerFatalDamage";
53
+ import { PostPlayerInitFirstRegisterParameters } from "../callbacks/subscriptions/postPlayerInitFirst";
53
54
  import { PostPlayerInitLateRegisterParameters } from "../callbacks/subscriptions/postPlayerInitLate";
54
- import { PostPlayerInitReorderedRegisterParameters } from "../callbacks/subscriptions/postPlayerInitReordered";
55
55
  import { PostPlayerRenderReorderedRegisterParameters } from "../callbacks/subscriptions/postPlayerRenderReordered";
56
56
  import { PostPlayerUpdateReorderedRegisterParameters } from "../callbacks/subscriptions/postPlayerUpdateReordered";
57
57
  import { PostPoopRenderRegisterParameters } from "../callbacks/subscriptions/postPoopRender";
@@ -136,8 +136,8 @@ export interface AddCallbackParameterCustom {
136
136
  [ModCallbackCustom.POST_PLAYER_COLLECTIBLE_ADDED]: PostPlayerCollectibleAddedRegisterParameters;
137
137
  [ModCallbackCustom.POST_PLAYER_COLLECTIBLE_REMOVED]: PostPlayerCollectibleRemovedRegisterParameters;
138
138
  [ModCallbackCustom.POST_PLAYER_FATAL_DAMAGE]: PostPlayerFatalDamageRegisterParameters;
139
+ [ModCallbackCustom.POST_PLAYER_INIT_FIRST]: PostPlayerInitFirstRegisterParameters;
139
140
  [ModCallbackCustom.POST_PLAYER_INIT_LATE]: PostPlayerInitLateRegisterParameters;
140
- [ModCallbackCustom.POST_PLAYER_INIT_REORDERED]: PostPlayerInitReorderedRegisterParameters;
141
141
  [ModCallbackCustom.POST_PLAYER_RENDER_REORDERED]: PostPlayerRenderReorderedRegisterParameters;
142
142
  [ModCallbackCustom.POST_PLAYER_UPDATE_REORDERED]: PostPlayerUpdateReorderedRegisterParameters;
143
143
  [ModCallbackCustom.POST_POOP_RENDER]: PostPoopRenderRegisterParameters;