isaacscript-common 30.12.10 → 31.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/index.rollup.d.ts +152 -20
  2. package/dist/isaacscript-common.lua +181 -110
  3. package/dist/src/classes/features/other/CustomStages.d.ts +11 -0
  4. package/dist/src/classes/features/other/CustomStages.d.ts.map +1 -1
  5. package/dist/src/classes/features/other/CustomStages.lua +42 -9
  6. package/dist/src/classes/features/other/customStages/backdrop.lua +3 -3
  7. package/dist/src/functions/levelGrid.d.ts +22 -13
  8. package/dist/src/functions/levelGrid.d.ts.map +1 -1
  9. package/dist/src/functions/levelGrid.lua +46 -23
  10. package/dist/src/functions/logMisc.d.ts.map +1 -1
  11. package/dist/src/functions/logMisc.lua +3 -7
  12. package/dist/src/functions/roomData.d.ts +1 -5
  13. package/dist/src/functions/roomData.d.ts.map +1 -1
  14. package/dist/src/functions/roomGrid.lua +6 -6
  15. package/dist/src/functions/roomShape.d.ts +1 -1
  16. package/dist/src/functions/roomShape.d.ts.map +1 -1
  17. package/dist/src/functions/roomShape.lua +1 -1
  18. package/dist/src/functions/roomShapeWalls.lua +2 -2
  19. package/dist/src/functions/rooms.d.ts +98 -1
  20. package/dist/src/functions/rooms.d.ts.map +1 -1
  21. package/dist/src/functions/rooms.lua +145 -68
  22. package/package.json +1 -1
  23. package/src/classes/features/other/CustomStages.ts +60 -0
  24. package/src/classes/features/other/customStages/backdrop.ts +3 -3
  25. package/src/functions/levelGrid.ts +51 -24
  26. package/src/functions/logMisc.ts +5 -9
  27. package/src/functions/roomData.ts +4 -0
  28. package/src/functions/roomGrid.ts +2 -2
  29. package/src/functions/roomShape.ts +1 -1
  30. package/src/functions/roomShapeWalls.ts +2 -2
  31. package/src/functions/rooms.ts +237 -118
@@ -10,9 +10,7 @@
10
10
  import type { DoorSlot, RoomShape } from "isaac-typescript-definitions";
11
11
  import {
12
12
  DisplayFlag,
13
- DownpourRoomSubType,
14
13
  LevelStateFlag,
15
- MinesRoomSubType,
16
14
  RoomDescriptorFlag,
17
15
  RoomType,
18
16
  } from "isaac-typescript-definitions";
@@ -36,8 +34,13 @@ import {
36
34
  getRoomShape,
37
35
  } from "./roomData";
38
36
  import { getGridIndexDelta } from "./roomShape";
39
- import { getRooms, getRoomsInsideGrid, isSecretRoomType } from "./rooms";
40
- import { asNumber } from "./types";
37
+ import {
38
+ getRooms,
39
+ getRoomsInsideGrid,
40
+ isMineShaft,
41
+ isMirrorRoom,
42
+ isSecretRoomType,
43
+ } from "./rooms";
41
44
 
42
45
  const LEFT = -1;
43
46
  const UP = -LEVEL_GRID_ROW_WIDTH;
@@ -123,17 +126,23 @@ export function getAllRoomGridIndexes(): readonly int[] {
123
126
  *
124
127
  * @param seedOrRNG Optional. The `Seed` or `RNG` object to use. If an `RNG` object is provided, the
125
128
  * `RNG.Next` method will be called. Default is `getRandomSeed()`.
129
+ * @param ensureDeadEnd Optional. Whether to pick a valid dead end attached to a normal room. If
130
+ * false, the function will randomly pick from any valid location that would
131
+ * have a red door.
126
132
  * @returns Either a tuple of adjacent room grid index, `DoorSlot`, and new room grid index, or
127
133
  * undefined.
128
134
  */
129
- export function getNewRoomCandidate(seedOrRNG: Seed | RNG = getRandomSeed()):
135
+ export function getNewRoomCandidate(
136
+ seedOrRNG: Seed | RNG = getRandomSeed(),
137
+ ensureDeadEnd = true,
138
+ ):
130
139
  | {
131
140
  readonly adjacentRoomGridIndex: int;
132
141
  readonly doorSlot: DoorSlot;
133
142
  readonly newRoomGridIndex: int;
134
143
  }
135
144
  | undefined {
136
- const newRoomCandidatesForLevel = getNewRoomCandidatesForLevel();
145
+ const newRoomCandidatesForLevel = getNewRoomCandidatesForLevel(ensureDeadEnd);
137
146
  if (newRoomCandidatesForLevel.length === 0) {
138
147
  return undefined;
139
148
  }
@@ -143,14 +152,17 @@ export function getNewRoomCandidate(seedOrRNG: Seed | RNG = getRandomSeed()):
143
152
 
144
153
  /**
145
154
  * Helper function to iterate through the possible doors for a room and see if any of them would be
146
- * a valid spot to insert a brand new room on the floor. (Any potential new rooms cannot be
147
- * connected to any other existing rooms on the floor.)
155
+ * a valid spot to insert a brand new room on the floor.
148
156
  *
149
157
  * @param roomGridIndex Optional. Default is the current room index.
158
+ * @param ensureDeadEnd Optional. Whether to only include doors that lead to a valid dead end
159
+ * attached to a normal room. If false, the function will include all doors
160
+ * that would have a red door.
150
161
  * @returns A array of tuples of `DoorSlot` and room grid index.
151
162
  */
152
163
  export function getNewRoomCandidatesBesideRoom(
153
164
  roomGridIndex?: int,
165
+ ensureDeadEnd = true,
154
166
  ): ReadonlyArray<{ readonly doorSlot: DoorSlot; readonly roomGridIndex: int }> {
155
167
  const roomDescriptor = getRoomDescriptor(roomGridIndex);
156
168
 
@@ -189,7 +201,7 @@ export function getNewRoomCandidatesBesideRoom(
189
201
  // Check to see if hypothetically creating a room at the given room grid index would be a dead
190
202
  // end. In other words, if we created the room, we would only want it to connect to one other
191
203
  // room (this one).
192
- if (!isDeadEnd(adjacentRoomGridIndex)) {
204
+ if (ensureDeadEnd && !isDeadEnd(adjacentRoomGridIndex)) {
193
205
  continue;
194
206
  }
195
207
 
@@ -203,13 +215,17 @@ export function getNewRoomCandidatesBesideRoom(
203
215
  }
204
216
 
205
217
  /**
206
- * Helper function to search through all of the rooms on the floor for a spot to insert a brand new
207
- * room.
218
+ * Helper function to get all of the spots on the floor to insert a brand new room.
208
219
  *
220
+ * @param ensureDeadEnd Optional. Whether to only include spots that are a valid dead end attached
221
+ * to a normal room. If false, the function will include all valid spots that
222
+ * have a red door.
209
223
  * @returns A array of tuples containing the adjacent room grid index, the `DoorSlot`, and the new
210
224
  * room grid index.
211
225
  */
212
- export function getNewRoomCandidatesForLevel(): ReadonlyArray<{
226
+ export function getNewRoomCandidatesForLevel(
227
+ ensureDeadEnd = true,
228
+ ): ReadonlyArray<{
213
229
  readonly adjacentRoomGridIndex: int;
214
230
  readonly doorSlot: DoorSlot;
215
231
  readonly newRoomGridIndex: int;
@@ -222,21 +238,22 @@ export function getNewRoomCandidatesForLevel(): ReadonlyArray<{
222
238
  (room) =>
223
239
  room.Data !== undefined &&
224
240
  room.Data.Type === RoomType.DEFAULT &&
225
- // The mirror room and the mineshaft entrance count as normal rooms, but those are supposed to
226
- // be dead ends as well.
227
- room.Data.Subtype !== asNumber(DownpourRoomSubType.MIRROR) &&
228
- room.Data.Subtype !== asNumber(MinesRoomSubType.MINESHAFT_ENTRANCE),
241
+ !isMirrorRoom(room.Data) && // Mirror rooms do not count as special rooms.
242
+ !isMineShaft(room.Data), // Mineshaft rooms do not count as special rooms.
229
243
  );
230
244
 
245
+ const roomsToLookThrough = ensureDeadEnd ? normalRooms : rooms;
246
+
231
247
  const newRoomCandidates: Array<{
232
248
  readonly adjacentRoomGridIndex: int;
233
249
  readonly doorSlot: DoorSlot;
234
250
  readonly newRoomGridIndex: int;
235
251
  }> = [];
236
252
 
237
- for (const room of normalRooms) {
253
+ for (const room of roomsToLookThrough) {
238
254
  const newRoomCandidatesBesideRoom = getNewRoomCandidatesBesideRoom(
239
255
  room.SafeGridIndex,
256
+ ensureDeadEnd,
240
257
  );
241
258
  for (const { doorSlot, roomGridIndex } of newRoomCandidatesBesideRoom) {
242
259
  newRoomCandidates.push({
@@ -489,23 +506,29 @@ export function isRoomInsideGrid(roomGridIndex?: int): boolean {
489
506
  }
490
507
 
491
508
  /**
492
- * Helper function to generate a new room on the floor at a valid dead end attached to a normal
493
- * room.
509
+ * Helper function to generate a new room on the floor.
494
510
  *
495
511
  * Under the hood, this function uses the `Level.MakeRedRoomDoor` method to create the room.
496
512
  *
497
- * The newly created room will have data corresponding to the game's randomly generated red room. If
498
- * you want to modify this, use the `setRoomData` helper function.
499
- *
500
513
  * @param seedOrRNG Optional. The `Seed` or `RNG` object to use. If an `RNG` object is provided, the
501
514
  * `RNG.Next` method will be called. Default is `Level.GetDungeonPlacementSeed`.
502
515
  * Note that the RNG is only used to select the random location to put the room on
503
516
  * the floor; it does not influence the randomly chosen room contents. (That is
504
517
  * performed by the game and can not be manipulated prior to its generation.)
518
+ * @param ensureDeadEnd Optional. Whether to place the room at a valid dead end attached to a normal
519
+ * room. If false, it will randomly appear at any valid location that would
520
+ * have a red door.
521
+ * @param customRoomData Optional. By default, the newly created room will have data corresponding
522
+ * to the game's randomly generated red room. If you provide this function
523
+ * with room data, it will be used to override the vanilla data.
505
524
  * @returns The room grid index of the new room or undefined if the floor had no valid dead ends to
506
525
  * place a room.
507
526
  */
508
- export function newRoom(seedOrRNG?: Seed | RNG): int | undefined {
527
+ export function newRoom(
528
+ seedOrRNG?: Seed | RNG,
529
+ ensureDeadEnd = true,
530
+ customRoomData?: RoomConfig,
531
+ ): int | undefined {
509
532
  const level = game.GetLevel();
510
533
 
511
534
  if (seedOrRNG === undefined) {
@@ -513,7 +536,7 @@ export function newRoom(seedOrRNG?: Seed | RNG): int | undefined {
513
536
  }
514
537
  const rng = isRNG(seedOrRNG) ? seedOrRNG : newRNG(seedOrRNG);
515
538
 
516
- const newRoomCandidate = getNewRoomCandidate(rng);
539
+ const newRoomCandidate = getNewRoomCandidate(rng, ensureDeadEnd);
517
540
  if (newRoomCandidate === undefined) {
518
541
  return undefined;
519
542
  }
@@ -530,6 +553,10 @@ export function newRoom(seedOrRNG?: Seed | RNG): int | undefined {
530
553
  RoomDescriptorFlag.RED_ROOM,
531
554
  );
532
555
 
556
+ if (customRoomData !== undefined) {
557
+ roomDescriptor.Data = customRoomData;
558
+ }
559
+
533
560
  // By default, the new room will not appear on the map, even if the player has The Mind. Thus, we
534
561
  // must manually alter the `DisplayFlags` of the room descriptor.
535
562
  const roomData = roomDescriptor.Data;
@@ -328,15 +328,11 @@ export function logRoom(this: void): void {
328
328
  const roomData = getRoomData();
329
329
 
330
330
  log("Logging room information:");
331
- if (roomData === undefined) {
332
- log("- Room data is undefined.");
333
- } else {
334
- log(`- Room stage ID: ${roomData.StageID}`);
335
- log(
336
- `- Type/variant/sub-type: ${roomData.Type}.${roomData.Variant}.${roomData.Subtype}`,
337
- );
338
- log(`- Name: ${roomData.Name}`);
339
- }
331
+ log(`- Room stage ID: ${roomData.StageID}`);
332
+ log(
333
+ `- Type/variant/sub-type: ${roomData.Type}.${roomData.Variant}.${roomData.Subtype}`,
334
+ );
335
+ log(`- Name: ${roomData.Name}`);
340
336
 
341
337
  const roomGridIndexName = GridRoom[roomGridIndex];
342
338
  if (roomGridIndexName === undefined) {
@@ -31,6 +31,10 @@ export function getRoomAllowedDoors(roomGridIndex?: int): Set<DoorSlot> {
31
31
  return allowedDoors;
32
32
  }
33
33
 
34
+ // We provide an overload because the current room is guaranteed to have data.
35
+ export function getRoomData(): RoomConfig;
36
+ export function getRoomData(roomGridIndex?: int): RoomConfig | undefined;
37
+
34
38
  /**
35
39
  * Helper function to get the room data for the provided room.
36
40
  *
@@ -14,7 +14,7 @@ import {
14
14
  getRoomShapeBottomRightPosition,
15
15
  getRoomShapeTopLeftPosition,
16
16
  getRoomShapeWidth,
17
- isLRoom,
17
+ isLRoomShape,
18
18
  } from "./roomShape";
19
19
 
20
20
  /**
@@ -70,7 +70,7 @@ export function isValidGridPosition(
70
70
  gridPosition: Vector,
71
71
  roomShape: RoomShape,
72
72
  ): boolean {
73
- return isLRoom(roomShape)
73
+ return isLRoomShape(roomShape)
74
74
  ? isValidGridPositionLRoom(gridPosition, roomShape)
75
75
  : isValidGridPositionNormal(gridPosition, roomShape);
76
76
  }
@@ -109,7 +109,7 @@ export function getRoomShapeWidth(roomShape: RoomShape): int {
109
109
  return ROOM_SHAPE_TO_GRID_WIDTH[roomShape];
110
110
  }
111
111
 
112
- export function isLRoom(roomShape: RoomShape): boolean {
112
+ export function isLRoomShape(roomShape: RoomShape): boolean {
113
113
  return L_ROOM_SHAPES_SET.has(roomShape);
114
114
  }
115
115
 
@@ -5,7 +5,7 @@ import { CornerType } from "../enums/CornerType";
5
5
  import type { Corner } from "../interfaces/Corner";
6
6
  import { ReadonlySet } from "../types/ReadonlySet";
7
7
  import { getGridIndexesBetween } from "./gridIndex";
8
- import { getRoomShapeCorners, isLRoom } from "./roomShape";
8
+ import { getRoomShapeCorners, isLRoomShape } from "./roomShape";
9
9
  import { inBossRoomOf, inHomeCloset } from "./rooms";
10
10
 
11
11
  const ROOM_SHAPE_TO_WALL_GRID_INDEX_SET: ReadonlyMap<
@@ -26,7 +26,7 @@ function getVanillaWallGridIndexSetForRoomShape(
26
26
  roomShape: RoomShape,
27
27
  ): ReadonlySet<int> {
28
28
  const corners = getRoomShapeCorners(roomShape);
29
- const lRoom = isLRoom(roomShape);
29
+ const lRoom = isLRoomShape(roomShape);
30
30
 
31
31
  if (lRoom && corners.length !== 6) {
32
32
  error(