isaacscript-common 6.11.1 → 6.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/callbacks/postFlip.lua +2 -2
  2. package/dist/callbacks/subscriptions/postFirstFlip.d.ts +1 -1
  3. package/dist/callbacks/subscriptions/postFirstFlip.d.ts.map +1 -1
  4. package/dist/callbacks/subscriptions/postFirstFlip.lua +2 -2
  5. package/dist/callbacks/subscriptions/postFlip.d.ts +1 -1
  6. package/dist/callbacks/subscriptions/postFlip.d.ts.map +1 -1
  7. package/dist/callbacks/subscriptions/postFlip.lua +2 -2
  8. package/dist/enums/ModCallbackCustom.d.ts +2 -2
  9. package/dist/enums/private/StageTravelState.d.ts +6 -1
  10. package/dist/enums/private/StageTravelState.d.ts.map +1 -1
  11. package/dist/enums/private/StageTravelState.lua +10 -0
  12. package/dist/enums/private/TrapdoorAnimation.d.ts +6 -0
  13. package/dist/enums/private/TrapdoorAnimation.d.ts.map +1 -0
  14. package/dist/enums/private/TrapdoorAnimation.lua +6 -0
  15. package/dist/features/customGridEntity.d.ts +8 -5
  16. package/dist/features/customGridEntity.d.ts.map +1 -1
  17. package/dist/features/customGridEntity.lua +58 -15
  18. package/dist/features/customStage/exports.d.ts.map +1 -1
  19. package/dist/features/customStage/exports.lua +0 -13
  20. package/dist/features/customStage/init.d.ts.map +1 -1
  21. package/dist/features/customStage/init.lua +16 -1
  22. package/dist/features/customStage/streakText.d.ts.map +1 -1
  23. package/dist/features/customStage/streakText.lua +0 -1
  24. package/dist/features/customTrapdoor/blackSprite.d.ts +2 -0
  25. package/dist/features/customTrapdoor/blackSprite.d.ts.map +1 -0
  26. package/dist/features/customTrapdoor/blackSprite.lua +19 -0
  27. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts +8 -3
  28. package/dist/features/customTrapdoor/customTrapdoorConstants.d.ts.map +1 -1
  29. package/dist/features/customTrapdoor/customTrapdoorConstants.lua +9 -1
  30. package/dist/features/customTrapdoor/exports.d.ts +11 -19
  31. package/dist/features/customTrapdoor/exports.d.ts.map +1 -1
  32. package/dist/features/customTrapdoor/exports.lua +48 -82
  33. package/dist/features/customTrapdoor/init.d.ts +3 -0
  34. package/dist/features/customTrapdoor/init.d.ts.map +1 -0
  35. package/dist/features/customTrapdoor/init.lua +173 -0
  36. package/dist/features/customTrapdoor/openClose.d.ts +5 -0
  37. package/dist/features/customTrapdoor/openClose.d.ts.map +1 -0
  38. package/dist/features/customTrapdoor/openClose.lua +60 -0
  39. package/dist/features/customTrapdoor/touched.d.ts +4 -0
  40. package/dist/features/customTrapdoor/touched.d.ts.map +1 -0
  41. package/dist/features/customTrapdoor/touched.lua +141 -0
  42. package/dist/features/customTrapdoor/v.d.ts +16 -2
  43. package/dist/features/customTrapdoor/v.d.ts.map +1 -1
  44. package/dist/features/customTrapdoor/v.lua +8 -6
  45. package/dist/features/extraConsoleCommands/init.d.ts.map +1 -1
  46. package/dist/features/extraConsoleCommands/init.lua +3 -1
  47. package/dist/features/taintedLazarusPlayers.d.ts.map +1 -1
  48. package/dist/features/taintedLazarusPlayers.lua +13 -21
  49. package/dist/functions/log.lua +3 -3
  50. package/dist/functions/playerIndex.d.ts +5 -0
  51. package/dist/functions/playerIndex.d.ts.map +1 -1
  52. package/dist/functions/playerIndex.lua +16 -6
  53. package/dist/functions/table.d.ts +1 -1
  54. package/dist/functions/table.d.ts.map +1 -1
  55. package/dist/initFeatures.d.ts.map +1 -1
  56. package/dist/initFeatures.lua +3 -0
  57. package/dist/interfaces/CustomGridEntityData.d.ts +5 -1
  58. package/dist/interfaces/CustomGridEntityData.d.ts.map +1 -1
  59. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts +3 -0
  60. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts.map +1 -1
  61. package/package.json +2 -2
  62. package/src/callbacks/postFlip.ts +3 -2
  63. package/src/callbacks/subscriptions/postFirstFlip.ts +6 -3
  64. package/src/callbacks/subscriptions/postFlip.ts +6 -3
  65. package/src/enums/ModCallbackCustom.ts +2 -2
  66. package/src/enums/private/StageTravelState.ts +5 -1
  67. package/src/enums/private/TrapdoorAnimation.ts +5 -0
  68. package/src/features/customGridEntity.ts +68 -10
  69. package/src/features/customStage/exports.ts +3 -22
  70. package/src/features/customStage/init.ts +20 -0
  71. package/src/features/customStage/streakText.ts +0 -1
  72. package/src/features/customTrapdoor/blackSprite.ts +16 -0
  73. package/src/features/customTrapdoor/customTrapdoorConstants.ts +13 -3
  74. package/src/features/customTrapdoor/exports.ts +52 -121
  75. package/src/features/customTrapdoor/init.ts +215 -0
  76. package/src/features/customTrapdoor/openClose.ts +103 -0
  77. package/src/features/customTrapdoor/touched.ts +175 -0
  78. package/src/features/customTrapdoor/v.ts +16 -10
  79. package/src/features/deployJSONRoom.ts +1 -1
  80. package/src/features/extraConsoleCommands/init.ts +5 -2
  81. package/src/features/saveDataManager/main.ts +1 -1
  82. package/src/features/taintedLazarusPlayers.ts +32 -31
  83. package/src/functions/deepCopy.ts +2 -2
  84. package/src/functions/entities.ts +1 -1
  85. package/src/functions/playerIndex.ts +12 -0
  86. package/src/functions/rng.ts +1 -1
  87. package/src/functions/table.ts +2 -2
  88. package/src/initFeatures.ts +2 -0
  89. package/src/interfaces/CustomGridEntityData.ts +6 -1
  90. package/src/interfaces/private/CustomTrapdoorDescription.ts +4 -0
@@ -0,0 +1,175 @@
1
+ import {
2
+ EntityCollisionClass,
3
+ EntityPartition,
4
+ PlayerType,
5
+ } from "isaac-typescript-definitions";
6
+ import { VectorZero } from "../../constants";
7
+ import { StageTravelState } from "../../enums/private/StageTravelState";
8
+ import {
9
+ getOtherPlayers,
10
+ getPlayers,
11
+ isChildPlayer,
12
+ } from "../../functions/playerIndex";
13
+ import { isCharacter } from "../../functions/players";
14
+ import { CustomTrapdoorDescription } from "../../interfaces/private/CustomTrapdoorDescription";
15
+ import { disableAllInputs } from "../disableInputs";
16
+ import { isPlayerUsingPony } from "../ponyDetection";
17
+ import { runInNGameFrames, runNextGameFrame } from "../runInNFrames";
18
+ import {
19
+ ANIMATIONS_THAT_PREVENT_STAGE_TRAVEL,
20
+ CUSTOM_TRAPDOOR_FEATURE_NAME,
21
+ OTHER_PLAYER_TRAPDOOR_JUMP_DELAY_GAME_FRAMES,
22
+ OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES,
23
+ TRAPDOOR_TOUCH_DISTANCE,
24
+ } from "./customTrapdoorConstants";
25
+ import v from "./v";
26
+
27
+ export function checkCustomTrapdoorPlayerTouched(
28
+ gridEntity: GridEntity,
29
+ trapdoorDescription: CustomTrapdoorDescription,
30
+ ): void {
31
+ if (v.run.state !== StageTravelState.NONE) {
32
+ return;
33
+ }
34
+
35
+ if (!trapdoorDescription.open) {
36
+ return;
37
+ }
38
+
39
+ const playersTouching = Isaac.FindInRadius(
40
+ gridEntity.Position,
41
+ TRAPDOOR_TOUCH_DISTANCE,
42
+ EntityPartition.PLAYER,
43
+ );
44
+ for (const playerEntity of playersTouching) {
45
+ const player = playerEntity.ToPlayer();
46
+ if (player === undefined) {
47
+ continue;
48
+ }
49
+
50
+ if (
51
+ // We don't want a Pony dash to transition to a new floor or a crawl space.
52
+ !isPlayerUsingPony(player) &&
53
+ !isChildPlayer(player) &&
54
+ canPlayerInteractWithTrapdoor(player)
55
+ ) {
56
+ playerTouchedCustomTrapdoor(gridEntity, trapdoorDescription, player);
57
+ return; // Prevent two players from touching the same entity.
58
+ }
59
+ }
60
+ }
61
+
62
+ function canPlayerInteractWithTrapdoor(player: EntityPlayer) {
63
+ // Players cannot interact with stage travel entities when items are queued or while playing
64
+ // certain animations.
65
+ const sprite = player.GetSprite();
66
+ const animation = sprite.GetAnimation();
67
+ return (
68
+ !player.IsHoldingItem() &&
69
+ !ANIMATIONS_THAT_PREVENT_STAGE_TRAVEL.has(animation)
70
+ );
71
+ }
72
+
73
+ function playerTouchedCustomTrapdoor(
74
+ gridEntity: GridEntity,
75
+ trapdoorDescription: CustomTrapdoorDescription,
76
+ player: EntityPlayer,
77
+ ) {
78
+ v.run.state = StageTravelState.PLAYERS_JUMPING_DOWN;
79
+ v.run.destination = trapdoorDescription.destination;
80
+
81
+ disableAllInputs(CUSTOM_TRAPDOOR_FEATURE_NAME);
82
+ setPlayerAttributes(player, gridEntity.Position);
83
+ dropTaintedForgotten(player);
84
+
85
+ player.PlayExtraAnimation("Trapdoor");
86
+
87
+ const otherPlayers = getOtherPlayers(player);
88
+ otherPlayers.forEach((otherPlayer, i) => {
89
+ const gameFramesToWaitBeforeJumping =
90
+ OTHER_PLAYER_TRAPDOOR_JUMP_DELAY_GAME_FRAMES * (i + 1);
91
+ const otherPlayerPtr = EntityPtr(otherPlayer);
92
+ runInNGameFrames(() => {
93
+ startDelayedJump(otherPlayerPtr, gridEntity.Position);
94
+ }, gameFramesToWaitBeforeJumping);
95
+ });
96
+ }
97
+
98
+ function startDelayedJump(entityPtr: EntityPtr, trapdoorPosition: Vector) {
99
+ const entity = entityPtr.Ref;
100
+ if (entity === undefined) {
101
+ return;
102
+ }
103
+
104
+ const player = entity.ToPlayer();
105
+ if (player === undefined) {
106
+ return;
107
+ }
108
+
109
+ player.PlayExtraAnimation("Trapdoor");
110
+
111
+ adjustPlayerVelocityToTrapdoor(entityPtr, player.Position, trapdoorPosition);
112
+ }
113
+
114
+ function adjustPlayerVelocityToTrapdoor(
115
+ entityPtr: EntityPtr,
116
+ startPos: Vector,
117
+ endPos: Vector,
118
+ ) {
119
+ const entity = entityPtr.Ref;
120
+ if (entity === undefined) {
121
+ return;
122
+ }
123
+
124
+ const player = entity.ToPlayer();
125
+ if (player === undefined) {
126
+ return;
127
+ }
128
+
129
+ const sprite = player.GetSprite();
130
+ if (sprite.IsFinished("Trapdoor")) {
131
+ return;
132
+ }
133
+
134
+ const frame = sprite.GetFrame();
135
+ if (frame > OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES) {
136
+ // We have already arrived at the trapdoor.
137
+ return;
138
+ }
139
+
140
+ const totalDifference = endPos.sub(startPos);
141
+ const differencePerFrame = totalDifference.div(
142
+ OTHER_PLAYER_TRAPDOOR_JUMP_DURATION_GAME_FRAMES,
143
+ );
144
+ const differenceForThisFrame = differencePerFrame.mul(frame + 1);
145
+ const targetPosition = startPos.add(differenceForThisFrame);
146
+ const calculatedVelocity = player.Position.sub(targetPosition);
147
+
148
+ player.Velocity = calculatedVelocity;
149
+
150
+ runNextGameFrame(() => {
151
+ adjustPlayerVelocityToTrapdoor(entityPtr, startPos, endPos);
152
+ });
153
+ }
154
+
155
+ function setPlayerAttributes(trapdoorPlayer: EntityPlayer, position: Vector) {
156
+ // Snap the player to the exact position of the trapdoor so that they cleanly jump down the hole.
157
+ trapdoorPlayer.Position = position;
158
+
159
+ for (const player of getPlayers()) {
160
+ // Freeze all players.
161
+ player.Velocity = VectorZero;
162
+
163
+ // We don't want enemy attacks to move the players.
164
+ player.EntityCollisionClass = EntityCollisionClass.NONE;
165
+ }
166
+ }
167
+
168
+ function dropTaintedForgotten(player: EntityPlayer) {
169
+ if (isCharacter(player, PlayerType.THE_FORGOTTEN_B)) {
170
+ const taintedSoul = player.GetOtherTwin();
171
+ if (taintedSoul !== undefined) {
172
+ taintedSoul.ThrowHeldEntity(VectorZero);
173
+ }
174
+ }
175
+ }
@@ -1,20 +1,26 @@
1
+ import { LevelStage, StageType } from "isaac-typescript-definitions";
2
+ import { DefaultMap } from "../../classes/DefaultMap";
1
3
  import { StageTravelState } from "../../enums/private/StageTravelState";
2
4
  import { CustomTrapdoorDescription } from "../../interfaces/private/CustomTrapdoorDescription";
3
5
 
4
6
  const v = {
5
7
  run: {
6
8
  state: StageTravelState.NONE,
9
+
10
+ /** The render frame that this state was reached. */
11
+ stateRenderFrame: null as int | null,
12
+
13
+ destination: null as
14
+ | [stage: LevelStage, stageType: StageType]
15
+ | string
16
+ | null,
7
17
  },
8
18
 
9
- room: {
10
- /** Indexed by grid index. */
11
- trapdoors: new Map<int, CustomTrapdoorDescription>(),
19
+ level: {
20
+ /** Indexed by room list index and grid index. */
21
+ trapdoors: new DefaultMap<int, Map<int, CustomTrapdoorDescription>>(
22
+ () => new Map(),
23
+ ),
12
24
  },
13
25
  };
14
-
15
- export function getCustomTrapdoorDescription(
16
- gridEntity: GridEntity,
17
- ): CustomTrapdoorDescription | undefined {
18
- const gridIndex = gridEntity.GetGridIndex();
19
- return v.room.trapdoors.get(gridIndex);
20
- }
26
+ export default v;
@@ -161,7 +161,7 @@ function preUseItemWeNeedToGoDeeper(
161
161
  fillRoomWithDecorations();
162
162
  });
163
163
 
164
- // Cancel the effect.
164
+ // Cancel the original effect.
165
165
  return true;
166
166
  }
167
167
 
@@ -8,6 +8,7 @@ import {
8
8
  } from "isaac-typescript-definitions";
9
9
  import { ModUpgraded } from "../../classes/ModUpgraded";
10
10
  import { MAX_SPEED_STAT } from "../../constants";
11
+ import { bitFlags } from "../../functions/flag";
11
12
  import { getMapPartialMatch } from "../../functions/map";
12
13
  import { printConsole } from "../../functions/utils";
13
14
  import { debugDisplayInit } from "../debugDisplay/debugDisplay";
@@ -349,8 +350,10 @@ function entityTakeDmgPlayer() {
349
350
  }
350
351
 
351
352
  // ModCallback.POST_CURSE_EVAL (12)
352
- function postCurseEval(curses: int) {
353
- return v.persistent.disableCurses ? LevelCurse.NONE : curses;
353
+ function postCurseEval(
354
+ curses: BitFlags<LevelCurse>,
355
+ ): BitFlags<LevelCurse> | undefined {
356
+ return v.persistent.disableCurses ? bitFlags(LevelCurse.NONE) : curses;
354
357
  }
355
358
 
356
359
  // ModCallback.EXECUTE_CMD (22)
@@ -171,7 +171,7 @@ function clearAndCopyAllElements(
171
171
  ) {
172
172
  clearTable(oldTable);
173
173
 
174
- for (const [key, value] of pairs(newTable)) {
174
+ for (const [key, value] of newTable) {
175
175
  oldTable.set(key, value);
176
176
  }
177
177
  }
@@ -2,29 +2,39 @@
2
2
  // Lazarus.
3
3
 
4
4
  import { ModCallback, PlayerType } from "isaac-typescript-definitions";
5
+ import { ModUpgraded } from "../classes/ModUpgraded";
5
6
  import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
7
+ import { logError } from "../functions/log";
6
8
  import { saveDataManager } from "./saveDataManager/exports";
7
9
 
8
10
  const FEATURE_NAME = "taintedLazarusPlayers";
9
11
 
10
12
  const v = {
11
13
  run: {
12
- queuedTaintedLazarus: [] as EntityPtr[],
13
- queuedDeadTaintedLazarus: [] as EntityPtr[],
14
+ queuedTaintedLazarus: [] as EntityPlayer[],
15
+ queuedDeadTaintedLazarus: [] as EntityPlayer[],
14
16
 
15
17
  /**
16
- * The PostPlayerInit callback fires for Dead Tainted Lazarus at the beginning of the run.
18
+ * The `POST_PLAYER_INIT` callback fires for Dead Tainted Lazarus at the beginning of the run.
17
19
  * However, the player index for the Dead Tainted Lazarus player object at that time does not
18
20
  * actually correspond to the player index for the real player once Flip has been used. Thus, we
19
21
  * revert to using PtrHash as an index for our map, which is consistent between the Dead Tainted
20
- * Lazarus object in the PostPlayerInit callback and the "real" Dead Tainted Lazarus.
22
+ * Lazarus object in the `POST_PLAYER_INIT` callback and the "real" Dead Tainted Lazarus.
23
+ *
24
+ * We use `EntityPlayer` as the value for the map instead of `EntityPtr` because using the
25
+ * pointer does not work for some reason. (When we unwrap it after one or more flips have been
26
+ * used, the pointers no longer point to the original objects, even if we manually update the
27
+ * pointers in the `POST_FLIP` callback.)
21
28
  */
22
- subPlayerMap: new Map<PtrHash, EntityPtr>(),
29
+ subPlayerMap: new Map<PtrHash, EntityPlayer>(),
23
30
  },
24
31
  };
25
32
 
26
33
  /** @internal */
27
- export function taintedLazarusPlayersInit(mod: Mod): void {
34
+ export function taintedLazarusPlayersInit(mod: ModUpgraded): void {
35
+ // `EntityPtr` is not serializable, so we cannot save data. However, this is inconsequential,
36
+ // since the `POST_PLAYER_INIT` callback will fire when a run is continued, which will repopulate
37
+ // the `subPlayerMap`.
28
38
  saveDataManager(FEATURE_NAME, v, () => false);
29
39
 
30
40
  mod.AddCallback(ModCallback.POST_PLAYER_INIT, postPlayerInit);
@@ -32,13 +42,12 @@ export function taintedLazarusPlayersInit(mod: Mod): void {
32
42
 
33
43
  // ModCallback.POST_PLAYER_INIT (9)
34
44
  function postPlayerInit(player: EntityPlayer) {
35
- const entityPtr = EntityPtr(player);
36
45
  const character = player.GetPlayerType();
37
46
 
38
47
  if (character === PlayerType.LAZARUS_B) {
39
- v.run.queuedTaintedLazarus.push(entityPtr);
48
+ v.run.queuedTaintedLazarus.push(player);
40
49
  } else if (character === PlayerType.LAZARUS_2_B) {
41
- v.run.queuedDeadTaintedLazarus.push(entityPtr);
50
+ v.run.queuedDeadTaintedLazarus.push(player);
42
51
  } else {
43
52
  return;
44
53
  }
@@ -47,6 +56,8 @@ function postPlayerInit(player: EntityPlayer) {
47
56
  }
48
57
 
49
58
  /**
59
+ * Indexes are the `PtrHash`, values are the `EntityPtr` of the *other* Lazarus.
60
+ *
50
61
  * When starting a run, the PostPlayerInit callback will fire first for Dead Tainted Lazarus, then
51
62
  * for Tainted Lazarus. When continuing a run, the PostPlayerInit callback will fire first for the
52
63
  * character that is currently active. Thus, since the order of the characters is not certain, we
@@ -61,15 +72,8 @@ function checkDequeue() {
61
72
  return;
62
73
  }
63
74
 
64
- const taintedLazarusPtr = v.run.queuedTaintedLazarus.shift();
65
- const deadTaintedLazarusPtr = v.run.queuedDeadTaintedLazarus.shift();
66
-
67
- if (taintedLazarusPtr === undefined || deadTaintedLazarusPtr === undefined) {
68
- return;
69
- }
70
-
71
- const taintedLazarus = taintedLazarusPtr.Ref;
72
- const deadTaintedLazarus = deadTaintedLazarusPtr.Ref;
75
+ const taintedLazarus = v.run.queuedTaintedLazarus.shift();
76
+ const deadTaintedLazarus = v.run.queuedDeadTaintedLazarus.shift();
73
77
 
74
78
  if (taintedLazarus === undefined || deadTaintedLazarus === undefined) {
75
79
  return;
@@ -78,8 +82,15 @@ function checkDequeue() {
78
82
  const taintedLazarusPtrHash = GetPtrHash(taintedLazarus);
79
83
  const deadTaintedLazarusPtrHash = GetPtrHash(deadTaintedLazarus);
80
84
 
81
- v.run.subPlayerMap.set(taintedLazarusPtrHash, deadTaintedLazarusPtr);
82
- v.run.subPlayerMap.set(deadTaintedLazarusPtrHash, taintedLazarusPtr);
85
+ if (taintedLazarusPtrHash === deadTaintedLazarusPtrHash) {
86
+ logError(
87
+ "Failed to cache the Tainted Lazarus player objects, since the hash for Tainted Lazarus and Dead Tainted Lazarus were the same.",
88
+ );
89
+ return;
90
+ }
91
+
92
+ v.run.subPlayerMap.set(taintedLazarusPtrHash, deadTaintedLazarus);
93
+ v.run.subPlayerMap.set(deadTaintedLazarusPtrHash, taintedLazarus);
83
94
  }
84
95
 
85
96
  /**
@@ -99,15 +110,5 @@ export function getTaintedLazarusSubPlayer(
99
110
  errorIfFeaturesNotInitialized(FEATURE_NAME);
100
111
 
101
112
  const ptrHash = GetPtrHash(player);
102
- const entityPtr = v.run.subPlayerMap.get(ptrHash);
103
- if (entityPtr === undefined) {
104
- return undefined;
105
- }
106
-
107
- const entity = entityPtr.Ref;
108
- if (entity === undefined) {
109
- return undefined;
110
- }
111
-
112
- return entity.ToPlayer();
113
+ return v.run.subPlayerMap.get(ptrHash);
113
114
  }
@@ -550,8 +550,8 @@ function getCopiedEntries(
550
550
  entries: Array<[key: AnyNotNil, value: unknown]>;
551
551
  convertedNumberKeysToStrings: boolean;
552
552
  } {
553
- // First, shallow copy the entries. We cannot use "pairs" to iterate over a Map or Set. We cannot
554
- // use "[...pairs(object)]", as it results in a run-time error.
553
+ // First, shallow copy the entries. We cannot use "pairs" to iterate over a `Map` or `Set`. We
554
+ // cannot use "[...pairs(object)]", as it results in a run-time error.
555
555
  const entries: Array<[key: AnyNotNil, value: unknown]> = [];
556
556
  if (isTSTLMap(object) || isTSTLSet(object) || isDefaultMap(object)) {
557
557
  for (const [key, value] of object.entries()) {
@@ -181,7 +181,7 @@ function setPrimitiveEntityFields(
181
181
  error('Failed to get the "__propget" table for an entity.');
182
182
  }
183
183
 
184
- for (const [key] of pairs(propGetTable)) {
184
+ for (const [key] of propGetTable) {
185
185
  // The values of this table are functions. Thus, we use the key to index the original entity.
186
186
  const indexKey = key as keyof typeof entity;
187
187
  const value = entity[indexKey];
@@ -28,6 +28,18 @@ export function getAllPlayers(): EntityPlayer[] {
28
28
  return players;
29
29
  }
30
30
 
31
+ /**
32
+ * Helper function to get all of the other players in the room besides the one provided. (This
33
+ * includes "child" players.)
34
+ */
35
+ export function getOtherPlayers(player: EntityPlayer): EntityPlayer[] {
36
+ const playerPtrHash = GetPtrHash(player);
37
+ const players = getAllPlayers();
38
+ return players.filter(
39
+ (otherPlayer) => GetPtrHash(otherPlayer) !== playerPtrHash,
40
+ );
41
+ }
42
+
31
43
  /**
32
44
  * Helper function to get the corresponding `EntityPlayer` object that corresponds to a
33
45
  * `PlayerIndex`.
@@ -142,7 +142,7 @@ export function setAllRNGToSeed(object: unknown, seed: Seed): void {
142
142
  }
143
143
 
144
144
  let setAtLeastOneSeed = false;
145
- for (const [_key, value] of pairs(object)) {
145
+ for (const [_key, value] of object) {
146
146
  if (isRNG(value)) {
147
147
  setSeed(value, seed);
148
148
  setAtLeastOneSeed = true;
@@ -4,8 +4,8 @@ import { isBoolean, isNumber, isString } from "./types";
4
4
  * In a `Map`, you can use the `clear` method to delete every element. However, in a `LuaMap`, the
5
5
  * `clear` method does not exist. Use this helper function as a drop-in replacement for this.
6
6
  */
7
- export function clearTable(luaMap: LuaMap): void {
8
- for (const [key] of pairs(luaMap)) {
7
+ export function clearTable(luaMap: LuaMap<AnyNotNil, unknown>): void {
8
+ for (const [key] of luaMap) {
9
9
  luaMap.delete(key);
10
10
  }
11
11
  }
@@ -4,6 +4,7 @@ import { characterStatsInit } from "./features/characterStats";
4
4
  import { collectibleItemPoolTypeInit } from "./features/collectibleItemPoolType";
5
5
  import { customGridEntityInit } from "./features/customGridEntity";
6
6
  import { customStageInit } from "./features/customStage/init";
7
+ import { customTrapdoorInit } from "./features/customTrapdoor/init";
7
8
  import { deployJSONRoomInit } from "./features/deployJSONRoom";
8
9
  import { disableAllSoundInit } from "./features/disableAllSound";
9
10
  import { disableInputsInit } from "./features/disableInputs";
@@ -38,6 +39,7 @@ function initFeaturesMajor(mod: ModUpgraded) {
38
39
  }
39
40
 
40
41
  function initFeaturesMinor(mod: ModUpgraded) {
42
+ customTrapdoorInit(mod);
41
43
  disableAllSoundInit(mod);
42
44
  disableInputsInit(mod);
43
45
  fadeInRemoverInit(mod);
@@ -4,10 +4,15 @@ import {
4
4
  } from "isaac-typescript-definitions";
5
5
 
6
6
  export interface CustomGridEntityData {
7
+ /**
8
+ * This is not a real `GridEntityType`; rather it is an arbitrary integer selected by end-user
9
+ * mods.
10
+ */
7
11
  gridEntityTypeCustom: GridEntityType;
12
+
8
13
  roomListIndex: int;
9
14
  gridIndex: int;
10
15
  anm2Path: string;
11
- defaultAnimation: string;
16
+ defaultAnimation?: string;
12
17
  gridCollisionClass: GridCollisionClass;
13
18
  }
@@ -1,3 +1,7 @@
1
+ import { LevelStage, StageType } from "isaac-typescript-definitions";
2
+
1
3
  export interface CustomTrapdoorDescription {
4
+ open: boolean;
5
+ destination: [stage: LevelStage, stageType: StageType] | string;
2
6
  firstSpawn: boolean;
3
7
  }