isaacscript-common 6.18.0 → 6.20.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 (65) hide show
  1. package/dist/constants.d.ts +5 -0
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.lua +4 -0
  4. package/dist/features/customStage/exports.d.ts.map +1 -1
  5. package/dist/features/customStage/exports.lua +2 -7
  6. package/dist/features/customTrapdoor/init.d.ts.map +1 -1
  7. package/dist/features/customTrapdoor/init.lua +10 -2
  8. package/dist/features/extraConsoleCommands/commandsSubroutines.d.ts.map +1 -1
  9. package/dist/features/extraConsoleCommands/commandsSubroutines.lua +2 -1
  10. package/dist/features/extraConsoleCommands/listCommands.d.ts.map +1 -1
  11. package/dist/features/extraConsoleCommands/listCommands.lua +2 -1
  12. package/dist/features/playerInventory.d.ts.map +1 -1
  13. package/dist/features/playerInventory.lua +2 -4
  14. package/dist/functions/curses.d.ts +3 -0
  15. package/dist/functions/curses.d.ts.map +1 -0
  16. package/dist/functions/curses.lua +11 -0
  17. package/dist/functions/dimensions.d.ts +12 -0
  18. package/dist/functions/dimensions.d.ts.map +1 -0
  19. package/dist/functions/dimensions.lua +35 -0
  20. package/dist/functions/eden.d.ts.map +1 -1
  21. package/dist/functions/eden.lua +2 -4
  22. package/dist/functions/itemPool.d.ts +18 -0
  23. package/dist/functions/itemPool.d.ts.map +1 -0
  24. package/dist/functions/itemPool.lua +133 -0
  25. package/dist/functions/level.d.ts.map +1 -1
  26. package/dist/functions/level.lua +8 -7
  27. package/dist/functions/levelGrid.d.ts +155 -0
  28. package/dist/functions/levelGrid.d.ts.map +1 -0
  29. package/dist/functions/levelGrid.lua +349 -0
  30. package/dist/functions/rockAlt.d.ts +4 -4
  31. package/dist/functions/rockAlt.d.ts.map +1 -1
  32. package/dist/functions/rockAlt.lua +69 -20
  33. package/dist/functions/roomData.d.ts +5 -0
  34. package/dist/functions/roomData.d.ts.map +1 -1
  35. package/dist/functions/roomData.lua +6 -0
  36. package/dist/functions/roomGrid.d.ts +8 -0
  37. package/dist/functions/roomGrid.d.ts.map +1 -1
  38. package/dist/functions/rooms.d.ts +42 -61
  39. package/dist/functions/rooms.d.ts.map +1 -1
  40. package/dist/functions/rooms.lua +129 -200
  41. package/dist/functions/saveFile.d.ts +1 -6
  42. package/dist/functions/saveFile.d.ts.map +1 -1
  43. package/dist/functions/saveFile.lua +4 -113
  44. package/dist/index.d.ts +4 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.lua +32 -0
  47. package/package.json +2 -2
  48. package/src/constants.ts +8 -0
  49. package/src/features/customStage/exports.ts +10 -11
  50. package/src/features/customTrapdoor/init.ts +7 -3
  51. package/src/features/extraConsoleCommands/commandsSubroutines.ts +2 -1
  52. package/src/features/extraConsoleCommands/listCommands.ts +2 -1
  53. package/src/features/playerInventory.ts +2 -3
  54. package/src/functions/curses.ts +9 -0
  55. package/src/functions/dimensions.ts +41 -0
  56. package/src/functions/eden.ts +2 -4
  57. package/src/functions/itemPool.ts +178 -0
  58. package/src/functions/level.ts +7 -10
  59. package/src/functions/levelGrid.ts +468 -0
  60. package/src/functions/rockAlt.ts +111 -29
  61. package/src/functions/roomData.ts +12 -0
  62. package/src/functions/roomGrid.ts +9 -0
  63. package/src/functions/rooms.ts +93 -206
  64. package/src/functions/saveFile.ts +5 -147
  65. package/src/index.ts +4 -0
@@ -8,6 +8,7 @@ import {
8
8
  GridRoom,
9
9
  HomeRoomSubType,
10
10
  ItemPoolType,
11
+ LevelCurse,
11
12
  MinibossID,
12
13
  RoomDescriptorFlag,
13
14
  RoomShape,
@@ -17,14 +18,11 @@ import {
17
18
  StageID,
18
19
  } from "isaac-typescript-definitions";
19
20
  import { game, sfxManager } from "../cachedClasses";
20
- import {
21
- LEVEL_GRID_ROW_WIDTH,
22
- MAX_LEVEL_GRID_INDEX,
23
- NUM_DIMENSIONS,
24
- } from "../constants";
25
- import { ROOM_SHAPE_TO_DOOR_SLOTS_TO_GRID_INDEX_DELTA } from "../objects/roomShapeToDoorSlotsToGridIndexDelta";
21
+ import { MAX_LEVEL_GRID_INDEX } from "../constants";
26
22
  import { ROOM_TYPE_NAMES } from "../objects/roomTypeNames";
27
23
  import { MINE_SHAFT_ROOM_SUB_TYPE_SET } from "../sets/mineShaftRoomSubTypesSet";
24
+ import { hasCurse } from "./curses";
25
+ import { inDimension } from "./dimensions";
28
26
  import {
29
27
  closeAllDoors,
30
28
  getDoors,
@@ -40,18 +38,15 @@ import {
40
38
  setEntityVelocities,
41
39
  } from "./positionVelocity";
42
40
  import {
43
- getRoomAllowedDoors,
44
41
  getRoomData,
45
- getRoomDescriptor,
46
42
  getRoomDescriptorReadOnly,
47
43
  getRoomGridIndex,
48
44
  getRoomName,
49
- getRoomShape,
50
45
  getRoomStageID,
51
46
  getRoomSubType,
52
47
  } from "./roomData";
53
- import { getGridIndexDelta } from "./roomShape";
54
- import { erange, irange } from "./utils";
48
+ import { getGotoCommand } from "./stage";
49
+ import { irange } from "./utils";
55
50
 
56
51
  /**
57
52
  * Helper function for quickly switching to a new room without playing a particular animation. Use
@@ -75,73 +70,56 @@ export function changeRoom(roomGridIndex: int): void {
75
70
  game.ChangeRoom(roomGridIndex);
76
71
  }
77
72
 
78
- /**
79
- * Helper function to get an array with every valid `Dimension` (not including `Dimension.CURRENT`).
80
- */
81
- export function getAllDimensions(): Dimension[] {
82
- return erange(NUM_DIMENSIONS) as Dimension[];
83
- }
84
-
85
- /** Helper function to get the grid index for every room on the entire floor. */
86
- export function getAllRoomGridIndexes(): int[] {
87
- const rooms = getRooms();
88
- return rooms.map((roomDescriptor) => roomDescriptor.SafeGridIndex);
89
- }
90
-
91
- /**
92
- * Helper function to get the current dimension. Most of the time, this will be `Dimension.MAIN`,
93
- * but it can change if e.g. the player is in the mirror world of Downpour/Dross.
94
- */
95
- export function getDimension(): Dimension {
96
- const level = game.GetLevel();
97
- const roomGridIndex = getRoomGridIndex();
98
- const roomDescription = level.GetRoomByIdx(roomGridIndex, Dimension.CURRENT);
99
- const currentRoomHash = GetPtrHash(roomDescription);
100
-
101
- for (const dimension of getAllDimensions()) {
102
- const dimensionRoomDescription = level.GetRoomByIdx(
103
- roomGridIndex,
104
- dimension,
105
- );
106
- const dimensionRoomHash = GetPtrHash(dimensionRoomDescription);
107
-
108
- if (dimensionRoomHash === currentRoomHash) {
109
- return dimension;
110
- }
111
- }
112
-
113
- error("Failed to get the current dimension.");
114
- }
115
-
116
73
  /**
117
74
  * Helper function to get the number of rooms that are currently on the floor layout. This does not
118
75
  * include off-grid rooms, like the Devil Room.
119
76
  */
120
77
  export function getNumRooms(): int {
121
- const rooms = getRooms();
78
+ const rooms = getRoomsInGrid();
122
79
  return rooms.length;
123
80
  }
124
81
 
125
82
  /**
126
- * Helper function to get an array of all of the safe grid indexes for rooms that match the
127
- * specified room type.
83
+ * Helper function to get the room data for a specific room type and variant combination. This is
84
+ * accomplished by using the "goto" console command to load the specified room into the
85
+ * `GridRoom.DEBUG` slot.
86
+ *
87
+ * Returns undefined if the provided room type and variant combination were not found. (A warning
88
+ * message will also appear on the console, since the "goto" command will fail.)
128
89
  *
129
- * This function only searches through rooms in the current dimension.
90
+ * Note that the side effect of using the "goto" console command is that it will trigger a room
91
+ * transition after a short delay. By default, this function cancels the incoming room transition by
92
+ * using the `Game.StartRoomTransition` method to travel to the same room.
130
93
  *
131
- * This function is variadic, meaning that you can specify N arguments to get the combined grid
132
- * indexes for N room types.
94
+ * @param roomType The type of room to retrieve.
95
+ * @param roomVariant The room variant to retrieve. (The room variant is the "ID" of the room in
96
+ * Basement Renovator.)
97
+ * @param cancelRoomTransition Optional. Whether to cancel the room transition by using the
98
+ * `Game.StartRoomTransition` method to travel to the same room. Default
99
+ * is true. Set this to false if you are getting the data for many rooms
100
+ * at the same time, and then use the `teleport` helper function when
101
+ * you are finished.
133
102
  */
134
- export function getRoomGridIndexesForType(...roomTypes: RoomType[]): int[] {
135
- const roomTypesSet = new Set<RoomType>([...roomTypes]);
136
-
137
- const rooms = getRooms();
138
- const matchingRooms = rooms.filter(
139
- (roomDescriptor) =>
140
- roomDescriptor.Data !== undefined &&
141
- roomTypesSet.has(roomDescriptor.Data.Type),
142
- );
103
+ export function getRoomDataForTypeVariant(
104
+ roomType: RoomType,
105
+ roomVariant: int,
106
+ cancelRoomTransition = true,
107
+ ): Readonly<RoomConfig> | undefined {
108
+ const command = getGotoCommand(roomType, roomVariant);
109
+ Isaac.ExecuteCommand(command);
110
+ const newRoomData = getRoomData(GridRoom.DEBUG);
111
+
112
+ if (cancelRoomTransition) {
113
+ const roomGridIndex = getRoomGridIndex();
114
+ teleport(
115
+ roomGridIndex,
116
+ Direction.NO_DIRECTION,
117
+ RoomTransitionAnim.FADE,
118
+ true,
119
+ );
120
+ }
143
121
 
144
- return matchingRooms.map((roomDescriptor) => roomDescriptor.SafeGridIndex);
122
+ return newRoomData;
145
123
  }
146
124
 
147
125
  /**
@@ -157,40 +135,6 @@ export function getRoomItemPoolType(): ItemPoolType {
157
135
  return itemPool.GetPoolForRoom(roomType, roomSeed);
158
136
  }
159
137
 
160
- /**
161
- * Helper function to get the grid indexes of all the rooms connected to the given room index.
162
- *
163
- * @param roomGridIndex Optional. Default is the current room index.
164
- */
165
- export function getRoomNeighbors(roomGridIndex?: int): int[] {
166
- const roomDescriptor = getRoomDescriptor(roomGridIndex);
167
-
168
- if (
169
- roomDescriptor.SafeGridIndex < 0 ||
170
- roomDescriptor.SafeGridIndex > MAX_LEVEL_GRID_INDEX
171
- ) {
172
- return [];
173
- }
174
-
175
- const roomData = roomDescriptor.Data;
176
- if (roomData === undefined) {
177
- return [];
178
- }
179
-
180
- const roomShape = roomData.Shape;
181
- const gridIndexDeltas = getRoomShapeNeighborGridIndexDeltas(roomShape);
182
- const gridIndexes = gridIndexDeltas.map(
183
- (gridIndexDelta) => roomDescriptor.SafeGridIndex + gridIndexDelta,
184
- );
185
- return gridIndexes.filter((gridIndex) => roomExists(gridIndex));
186
- }
187
-
188
- export function getRoomShapeNeighborGridIndexDeltas(
189
- roomShape: RoomShape,
190
- ): int[] {
191
- return [...ROOM_SHAPE_TO_DOOR_SLOTS_TO_GRID_INDEX_DELTA[roomShape].values()];
192
- }
193
-
194
138
  /**
195
139
  * Helper function to get the proper name of a room type.
196
140
  *
@@ -201,9 +145,12 @@ export function getRoomTypeName(roomType: RoomType): string {
201
145
  }
202
146
 
203
147
  /**
204
- * Helper function to get the room descriptor for every room on the level, including off-grid rooms.
205
- * Uses the `Level.GetRooms` method to accomplish this. Rooms without data are assumed to be
206
- * non-existent and are not added to the list.
148
+ * Helper function to get the room descriptor for every room on the level. This includes off-grid
149
+ * rooms, such as the Devil Room. (Off-grid rooms will only be included if they the data exists,
150
+ * which only usually happens once they have been visited at least once.)
151
+ *
152
+ * Under the hood, this function uses the `Level.GetRooms` method to accomplish this. Rooms without
153
+ * data are assumed to be non-existent and are not added to the list.
207
154
  *
208
155
  * @param includeExtraDimensionalRooms Optional. On some floors (e.g. Downpour 2, Mines 2),
209
156
  * extra-dimensional rooms are automatically generated and can be
@@ -215,21 +162,23 @@ export function getRooms(
215
162
  const level = game.GetLevel();
216
163
  const roomList = level.GetRooms();
217
164
 
165
+ /** Indexed by room safe grid index. We use a map to avoid adding extra dimensional rooms. */
218
166
  const roomsMap = new Map<int, RoomDescriptor>();
219
- if (includeExtraDimensionalRooms) {
220
- for (let i = 0; i < roomList.Size; i++) {
221
- const roomDescriptor = roomList.Get(i);
222
- if (roomDescriptor !== undefined && roomDescriptor.Data !== undefined) {
223
- roomsMap.set(roomDescriptor.ListIndex, roomDescriptor);
224
- }
167
+
168
+ for (let i = 0; i < roomList.Size; i++) {
169
+ const roomDescriptor = roomList.Get(i);
170
+ if (roomDescriptor === undefined || roomDescriptor.Data === undefined) {
171
+ continue;
225
172
  }
226
- } else {
227
- for (const roomGridIndex of irange(MAX_LEVEL_GRID_INDEX)) {
228
- const roomDescriptor = level.GetRoomByIdx(roomGridIndex);
229
- if (roomDescriptor.Data !== undefined) {
230
- roomsMap.set(roomDescriptor.ListIndex, roomDescriptor);
231
- }
173
+
174
+ if (
175
+ !includeExtraDimensionalRooms &&
176
+ roomsMap.has(roomDescriptor.SafeGridIndex)
177
+ ) {
178
+ continue;
232
179
  }
180
+
181
+ roomsMap.set(roomDescriptor.SafeGridIndex, roomDescriptor);
233
182
  }
234
183
 
235
184
  return [...roomsMap.values()];
@@ -237,8 +186,10 @@ export function getRooms(
237
186
 
238
187
  /**
239
188
  * Helper function to get the room descriptor for every room on the level except for rooms that are
240
- * not on the grid. Uses the `Level.GetRooms` method to accomplish this. Rooms without data are
241
- * assumed to be non-existent and are not added to the list.
189
+ * not on the grid.
190
+ *
191
+ * Under the hood, this function uses the `Level.GetRooms` method to accomplish this. Rooms without
192
+ * data are assumed to be non-existent and are not added to the list.
242
193
  *
243
194
  * @param includeExtraDimensionalRooms Optional. On some floors (e.g. Downpour 2, Mines 2),
244
195
  * extra-dimensional rooms are automatically be generated and can be
@@ -253,15 +204,19 @@ export function getRoomsInGrid(
253
204
 
254
205
  /**
255
206
  * Helper function to get the room descriptor for every room on the level in a specific dimension.
256
- * Uses the `Level.GetRooms` method to accomplish this. Rooms without data are assumed to be
257
- * non-existent and are not added to the list.
207
+ * This will not include any off-grid rooms, such as the Devil Room.
208
+ *
209
+ * Under the hood, this function uses the `Level.GetRooms` method to accomplish this. Rooms without
210
+ * data are assumed to be non-existent and are not added to the list.
258
211
  *
259
212
  * @returns A map of room ListIndex to RoomDescriptor.
260
213
  */
261
214
  export function getRoomsOfDimension(dimension: Dimension): RoomDescriptor[] {
262
215
  const level = game.GetLevel();
263
216
 
217
+ /** We use a map instead of an array because room shapes occupy more than one room grid index. */
264
218
  const roomsMap = new Map<int, RoomDescriptor>();
219
+
265
220
  for (const roomGridIndex of irange(MAX_LEVEL_GRID_INDEX)) {
266
221
  const roomDescriptor = level.GetRoomByIdx(roomGridIndex, dimension);
267
222
  if (roomDescriptor.Data !== undefined) {
@@ -361,10 +316,6 @@ export function inDevilsCrownTreasureRoom(): boolean {
361
316
  return hasFlag(roomDescriptor.Flags, RoomDescriptorFlag.DEVIL_TREASURE);
362
317
  }
363
318
 
364
- export function inDimension(dimension: Dimension): boolean {
365
- return dimension === getDimension();
366
- }
367
-
368
319
  export function inDoubleTrouble(): boolean {
369
320
  const room = game.GetRoom();
370
321
  const roomType = room.GetType();
@@ -484,87 +435,6 @@ export function isAllRoomsClear(onlyCheckRoomTypes?: RoomType[]): boolean {
484
435
  return matchingRooms.every((roomDescriptor) => roomDescriptor.Clear);
485
436
  }
486
437
 
487
- export function isDoorSlotValidAtGridIndex(
488
- doorSlot: DoorSlot,
489
- roomGridIndex: int,
490
- ): boolean {
491
- const allowedDoors = getRoomAllowedDoors(roomGridIndex);
492
- return allowedDoors.has(doorSlot);
493
- }
494
-
495
- export function isDoorSlotValidAtGridIndexForRedRoom(
496
- doorSlot: DoorSlot,
497
- roomGridIndex: int,
498
- ): boolean {
499
- const doorSlotValidAtGridIndex = isDoorSlotValidAtGridIndex(
500
- doorSlot,
501
- roomGridIndex,
502
- );
503
- if (!doorSlotValidAtGridIndex) {
504
- return false;
505
- }
506
-
507
- const roomShape = getRoomShape(roomGridIndex);
508
- if (roomShape === undefined) {
509
- return false;
510
- }
511
-
512
- const delta = getGridIndexDelta(roomShape, doorSlot);
513
- if (delta === undefined) {
514
- return false;
515
- }
516
-
517
- const redRoomGridIndex = roomGridIndex + delta;
518
- return (
519
- !roomExists(redRoomGridIndex) &&
520
- redRoomGridIndex >= 0 &&
521
- redRoomGridIndex <= MAX_LEVEL_GRID_INDEX
522
- );
523
- }
524
-
525
- /**
526
- * Helper function to detect if the provided room was created by the Red Key item. Under the hood,
527
- * this checks for the `RoomDescriptorFlag.FLAG_RED_ROOM` flag.
528
- *
529
- * @param roomGridIndex Optional. Default is the current room index.
530
- */
531
- export function isRedKeyRoom(roomGridIndex?: int): boolean {
532
- const roomDescriptor = getRoomDescriptor(roomGridIndex);
533
- return hasFlag(roomDescriptor.Flags, RoomDescriptorFlag.RED_ROOM);
534
- }
535
-
536
- /**
537
- * Helper function to determine if the provided room is part of the floor layout. For example, Devil
538
- * Rooms and the Mega Satan room are not considered to be inside the map.
539
- *
540
- * @param roomGridIndex Optional. Default is the current room index.
541
- */
542
- export function isRoomInsideMap(roomGridIndex?: int): boolean {
543
- if (roomGridIndex === undefined) {
544
- roomGridIndex = getRoomGridIndex();
545
- }
546
-
547
- return roomGridIndex >= 0;
548
- }
549
-
550
- /** Helper function to check if a room exists at the given room grid index. */
551
- export function roomExists(roomGridIndex: int): boolean {
552
- const roomData = getRoomData(roomGridIndex);
553
- return roomData !== undefined;
554
- }
555
-
556
- /**
557
- * Helper function to get the coordinates of a given grid index. The floor is represented by a 13x13
558
- * grid. For example, since the starting room is in the center, the starting room grid index of 84
559
- * be equal to coordinates of (?, ?).
560
- */
561
- export function roomGridIndexToXY(roomGridIndex: int): [x: int, y: int] {
562
- const x = roomGridIndex % LEVEL_GRID_ROW_WIDTH;
563
- const y = Math.floor(roomGridIndex / LEVEL_GRID_ROW_WIDTH);
564
-
565
- return [x, y];
566
- }
567
-
568
438
  /**
569
439
  * If the `Room.Update` method is called in a `POST_NEW_ROOM` callback, then some entities will
570
440
  * slide around (such as the player). Since those entity velocities are already at zero, setting
@@ -635,21 +505,34 @@ export function setRoomUncleared(): void {
635
505
  /**
636
506
  * Helper function to change the current room. It can be used for both teleportation and "normal"
637
507
  * room transitions, depending on what is passed for the `direction` and `roomTransitionAnim`
638
- * arguments. Use this function instead of invoking the `Game.StartRoomTransition` method directly
639
- * so that you do not forget to set `Level.LeaveDoor` property and to prevent crashing on invalid
640
- * room grid indexes.
508
+ * arguments.
509
+ *
510
+ * Use this function instead of invoking the `Game.StartRoomTransition` method directly so that:
511
+ * - you do not forget to set `Level.LeaveDoor` property
512
+ * - to prevent crashing on invalid room grid indexes
513
+ * - to automatically handle Curse of the Maze
641
514
  *
642
515
  * @param roomGridIndex The room grid index of the destination room.
643
516
  * @param direction Optional. Default is `Direction.NO_DIRECTION`.
644
517
  * @param roomTransitionAnim Optional. Default is `RoomTransitionAnim.TELEPORT`.
518
+ * @param force Optional. Whether to temporarily disable Curse of the Maze. Default is false. If set
519
+ * to false, then this function may not go to the provided room grid index.
645
520
  */
646
521
  export function teleport(
647
522
  roomGridIndex: int,
648
523
  direction = Direction.NO_DIRECTION,
649
524
  roomTransitionAnim = RoomTransitionAnim.TELEPORT,
525
+ force = false,
650
526
  ): void {
651
527
  const level = game.GetLevel();
652
528
 
529
+ // Before starting a room transition, we must ensure that Curse of the Maze is not in effect, or
530
+ // else the room transition might send us to the wrong room.
531
+ const shouldTempDisableCurse = force && hasCurse(LevelCurse.MAZE);
532
+ if (shouldTempDisableCurse) {
533
+ level.RemoveCurses(LevelCurse.MAZE);
534
+ }
535
+
653
536
  const roomData = getRoomData(roomGridIndex);
654
537
  if (roomData === undefined) {
655
538
  error(
@@ -662,4 +545,8 @@ export function teleport(
662
545
  level.LeaveDoor = DoorSlot.NO_DOOR_SLOT;
663
546
 
664
547
  game.StartRoomTransition(roomGridIndex, direction, roomTransitionAnim);
548
+
549
+ if (shouldTempDisableCurse) {
550
+ level.AddCurse(LevelCurse.MAZE, false);
551
+ }
665
552
  }
@@ -1,32 +1,10 @@
1
- import {
2
- CollectibleType,
3
- ItemConfigTag,
4
- ItemPoolType,
5
- PlayerType,
6
- TrinketType,
7
- } from "isaac-typescript-definitions";
8
- import { game } from "../cachedClasses";
9
- import { PlayerIndex } from "../types/PlayerIndex";
10
- import { getCollectibleSet } from "./collectibleSet";
11
- import { collectibleHasTag } from "./collectibleTag";
12
- import { mapGetPlayer, mapSetPlayer } from "./playerDataStructures";
13
- import { getPlayers } from "./playerIndex";
14
- import { anyPlayerHasCollectible, getPlayersOfType } from "./players";
15
- import { repeat } from "./utils";
16
-
17
- const COLLECTIBLES_THAT_AFFECT_ITEM_POOLS: readonly CollectibleType[] = [
18
- CollectibleType.CHAOS, // 402
19
- CollectibleType.SACRED_ORB, // 691
20
- CollectibleType.TMTRAINER, // 721
21
- ];
22
-
23
- const TRINKETS_THAT_AFFECT_ITEM_POOLS: readonly TrinketType[] = [
24
- TrinketType.NO,
25
- ];
1
+ import { CollectibleType, ItemPoolType } from "isaac-typescript-definitions";
2
+ import { isCollectibleInItemPool } from "./itemPool";
3
+ import { anyPlayerHasCollectible } from "./players";
26
4
 
27
5
  /**
28
6
  * Helper function to see if the given collectible is unlocked on the current save file. This
29
- * requires providing the corresponding item pool that the collectible is located in.
7
+ * requires providing the corresponding item pool that the collectible is normally located in.
30
8
  *
31
9
  * - If any player currently has the collectible, then it is assumed to be unlocked. (This is
32
10
  * because in almost all cases, when a collectible is added to a player's inventory, it is
@@ -37,11 +15,6 @@ const TRINKETS_THAT_AFFECT_ITEM_POOLS: readonly TrinketType[] = [
37
15
  * - If the collectible is non-offensive, any Tainted Losts will be temporarily changed to Isaac and
38
16
  * then changed back. (This is because Tainted Lost is not able to retrieve non-offensive
39
17
  * collectibles from item pools).
40
- *
41
- * Under the hood, this function works by using the `ItemPool.AddRoomBlacklist` method to blacklist
42
- * every collectible except for the one provided. Unfortunately, this is not a general-purpose
43
- * "isCollectibleInItemPool" algorithm, because when a pool is depleted, it will automatically pull
44
- * items from the Treasure Room pool, and there is no way to distinguish when this happens.
45
18
  */
46
19
  export function isCollectibleUnlocked(
47
20
  collectibleType: CollectibleType,
@@ -51,120 +24,5 @@ export function isCollectibleUnlocked(
51
24
  return true;
52
25
  }
53
26
 
54
- // On Tainted Lost, it is impossible to retrieve non-offensive collectibles from pools, so we
55
- // temporarily change the character to Isaac.
56
- const taintedLosts = getPlayersOfType(PlayerType.THE_LOST_B);
57
- const isOffensive = collectibleHasTag(
58
- collectibleType,
59
- ItemConfigTag.OFFENSIVE,
60
- );
61
- let changedPlayerTypes = false;
62
- if (!isOffensive) {
63
- changedPlayerTypes = true;
64
- for (const player of taintedLosts) {
65
- player.ChangePlayerType(PlayerType.ISAAC);
66
- }
67
- }
68
-
69
- const [removedItemsMap, removedTrinketsMap] =
70
- removeItemsAndTrinketsThatAffectItemPools();
71
-
72
- // Blacklist every collectible in the game except for the provided collectible.
73
- const itemPool = game.GetItemPool();
74
- const collectibleSet = getCollectibleSet();
75
- for (const collectibleTypeInSet of collectibleSet.values()) {
76
- if (collectibleTypeInSet !== collectibleType) {
77
- itemPool.AddRoomBlacklist(collectibleTypeInSet);
78
- }
79
- }
80
-
81
- // Get a collectible from the pool and see if it is the intended collectible. (We can use any
82
- // arbitrary value as the seed since it should not influence the result.)
83
- const seed = 1 as Seed;
84
- const retrievedCollectibleType = itemPool.GetCollectible(
85
- itemPoolType,
86
- false,
87
- seed,
88
- );
89
-
90
- const collectibleUnlocked = retrievedCollectibleType === collectibleType;
91
-
92
- // Reset the blacklist
93
- itemPool.ResetRoomBlacklist();
94
-
95
- restoreItemsAndTrinketsThatAffectItemPools(
96
- removedItemsMap,
97
- removedTrinketsMap,
98
- );
99
-
100
- // Change any players back to Tainted Lost, if necessary.
101
- if (changedPlayerTypes) {
102
- for (const player of taintedLosts) {
103
- player.ChangePlayerType(PlayerType.THE_LOST_B);
104
- }
105
- }
106
-
107
- return collectibleUnlocked;
108
- }
109
-
110
- /**
111
- * Before checking the item pools, remove any collectibles or trinkets that would affect the
112
- * retrieved collectible types.
113
- */
114
- function removeItemsAndTrinketsThatAffectItemPools(): [
115
- removedItemsMap: Map<PlayerIndex, CollectibleType[]>,
116
- removedTrinketsMap: Map<PlayerIndex, TrinketType[]>,
117
- ] {
118
- const removedItemsMap = new Map<PlayerIndex, CollectibleType[]>();
119
- const removedTrinketsMap = new Map<PlayerIndex, TrinketType[]>();
120
- for (const player of getPlayers()) {
121
- const removedItems: CollectibleType[] = [];
122
- for (const itemToRemove of COLLECTIBLES_THAT_AFFECT_ITEM_POOLS) {
123
- if (player.HasCollectible(itemToRemove)) {
124
- const numCollectibles = player.GetCollectibleNum(itemToRemove);
125
- repeat(numCollectibles, () => {
126
- player.RemoveCollectible(itemToRemove);
127
- removedItems.push(itemToRemove);
128
- });
129
- }
130
- }
131
-
132
- mapSetPlayer(removedItemsMap, player, removedItems);
133
-
134
- const removedTrinkets: TrinketType[] = [];
135
- for (const trinketToRemove of TRINKETS_THAT_AFFECT_ITEM_POOLS) {
136
- if (player.HasTrinket(trinketToRemove)) {
137
- const numTrinkets = player.GetTrinketMultiplier(trinketToRemove);
138
- repeat(numTrinkets, () => {
139
- player.TryRemoveTrinket(trinketToRemove);
140
- removedTrinkets.push(trinketToRemove);
141
- });
142
- }
143
- }
144
-
145
- mapSetPlayer(removedTrinketsMap, player, removedTrinkets);
146
- }
147
-
148
- return [removedItemsMap, removedTrinketsMap];
149
- }
150
-
151
- function restoreItemsAndTrinketsThatAffectItemPools(
152
- removedItemsMap: Map<PlayerIndex, CollectibleType[]>,
153
- removedTrinketsMap: Map<PlayerIndex, TrinketType[]>,
154
- ) {
155
- for (const player of getPlayers()) {
156
- const removedItems = mapGetPlayer(removedItemsMap, player);
157
- if (removedItems !== undefined) {
158
- for (const collectibleType of removedItems) {
159
- player.AddCollectible(collectibleType, 0, false); // Prevent Chaos from spawning pickups
160
- }
161
- }
162
-
163
- const removedTrinkets = mapGetPlayer(removedTrinketsMap, player);
164
- if (removedTrinkets !== undefined) {
165
- for (const trinketType of removedTrinkets) {
166
- player.AddTrinket(trinketType, false);
167
- }
168
- }
169
- }
27
+ return isCollectibleInItemPool(collectibleType, itemPoolType);
170
28
  }
package/src/index.ts CHANGED
@@ -97,9 +97,11 @@ export * from "./functions/collectibles";
97
97
  export * from "./functions/collectibleSet";
98
98
  export * from "./functions/collectibleTag";
99
99
  export * from "./functions/color";
100
+ export * from "./functions/curses";
100
101
  export * from "./functions/debug";
101
102
  export * from "./functions/deepCopy";
102
103
  export * from "./functions/deepCopyTests";
104
+ export * from "./functions/dimensions";
103
105
  export * from "./functions/direction";
104
106
  export * from "./functions/doors";
105
107
  export * from "./functions/easing";
@@ -117,11 +119,13 @@ export * from "./functions/gridEntities";
117
119
  export * from "./functions/gridEntitiesSpecific";
118
120
  export * from "./functions/input";
119
121
  export * from "./functions/isaacAPIClass";
122
+ export * from "./functions/itemPool";
120
123
  export * from "./functions/jsonHelpers";
121
124
  export * from "./functions/jsonRoom";
122
125
  export * from "./functions/kColor";
123
126
  export * from "./functions/language";
124
127
  export * from "./functions/level";
128
+ export * from "./functions/levelGrid";
125
129
  export * from "./functions/log";
126
130
  export * from "./functions/map";
127
131
  export * from "./functions/math";