isaacscript-common 30.12.11 → 31.0.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 (31) hide show
  1. package/dist/index.rollup.d.ts +141 -20
  2. package/dist/isaacscript-common.lua +157 -111
  3. package/dist/src/classes/features/other/ItemPoolDetection.lua +4 -2
  4. package/dist/src/classes/features/other/customStages/backdrop.lua +3 -3
  5. package/dist/src/functions/hex.lua +13 -7
  6. package/dist/src/functions/levelGrid.d.ts +22 -13
  7. package/dist/src/functions/levelGrid.d.ts.map +1 -1
  8. package/dist/src/functions/levelGrid.lua +46 -23
  9. package/dist/src/functions/logMisc.d.ts.map +1 -1
  10. package/dist/src/functions/logMisc.lua +3 -7
  11. package/dist/src/functions/roomData.d.ts +1 -5
  12. package/dist/src/functions/roomData.d.ts.map +1 -1
  13. package/dist/src/functions/roomGrid.lua +6 -6
  14. package/dist/src/functions/roomShape.d.ts +1 -1
  15. package/dist/src/functions/roomShape.d.ts.map +1 -1
  16. package/dist/src/functions/roomShape.lua +1 -1
  17. package/dist/src/functions/roomShapeWalls.lua +2 -2
  18. package/dist/src/functions/rooms.d.ts +98 -1
  19. package/dist/src/functions/rooms.d.ts.map +1 -1
  20. package/dist/src/functions/rooms.lua +145 -68
  21. package/package.json +2 -2
  22. package/src/classes/features/other/ItemPoolDetection.ts +6 -6
  23. package/src/classes/features/other/customStages/backdrop.ts +3 -3
  24. package/src/functions/hex.ts +8 -8
  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
@@ -7,9 +7,9 @@ local Map = ____lualib.Map
7
7
  local __TS__Spread = ____lualib.__TS__Spread
8
8
  local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter
9
9
  local __TS__ArrayMap = ____lualib.__TS__ArrayMap
10
+ local __TS__ArrayEvery = ____lualib.__TS__ArrayEvery
10
11
  local __TS__StringIncludes = ____lualib.__TS__StringIncludes
11
12
  local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
12
- local __TS__ArrayEvery = ____lualib.__TS__ArrayEvery
13
13
  local ____exports = {}
14
14
  local ____isaac_2Dtypescript_2Ddefinitions = require("isaac-typescript-definitions")
15
15
  local AngelRoomSubType = ____isaac_2Dtypescript_2Ddefinitions.AngelRoomSubType
@@ -19,7 +19,6 @@ local DownpourRoomSubType = ____isaac_2Dtypescript_2Ddefinitions.DownpourRoomSub
19
19
  local DungeonSubType = ____isaac_2Dtypescript_2Ddefinitions.DungeonSubType
20
20
  local GridRoom = ____isaac_2Dtypescript_2Ddefinitions.GridRoom
21
21
  local HomeRoomSubType = ____isaac_2Dtypescript_2Ddefinitions.HomeRoomSubType
22
- local LevelStage = ____isaac_2Dtypescript_2Ddefinitions.LevelStage
23
22
  local RoomDescriptorFlag = ____isaac_2Dtypescript_2Ddefinitions.RoomDescriptorFlag
24
23
  local RoomShape = ____isaac_2Dtypescript_2Ddefinitions.RoomShape
25
24
  local RoomType = ____isaac_2Dtypescript_2Ddefinitions.RoomType
@@ -58,9 +57,8 @@ local getRoomData = ____roomData.getRoomData
58
57
  local getRoomDescriptor = ____roomData.getRoomDescriptor
59
58
  local getRoomDescriptorReadOnly = ____roomData.getRoomDescriptorReadOnly
60
59
  local getRoomGridIndex = ____roomData.getRoomGridIndex
61
- local getRoomName = ____roomData.getRoomName
62
- local getRoomStageID = ____roomData.getRoomStageID
63
- local getRoomSubType = ____roomData.getRoomSubType
60
+ local ____roomShape = require("src.functions.roomShape")
61
+ local isLRoomShape = ____roomShape.isLRoomShape
64
62
  local ____roomTransition = require("src.functions.roomTransition")
65
63
  local reloadRoom = ____roomTransition.reloadRoom
66
64
  local ____stage = require("src.functions.stage")
@@ -136,6 +134,105 @@ function ____exports.getRoomsOutsideGrid(self)
136
134
  function(____, readOnlyRoomDescriptor) return getRoomDescriptor(nil, readOnlyRoomDescriptor.SafeGridIndex) end
137
135
  )
138
136
  end
137
+ --- Helper function to determine if the provided room is equal to `RoomShape.1x2` or `RoomShape.2x1`.
138
+ function ____exports.is2x1Room(self, roomData)
139
+ return roomData.Shape == RoomShape.SHAPE_1x2 or roomData.Shape == RoomShape.SHAPE_2x1
140
+ end
141
+ function ____exports.isAngelShop(self, roomData)
142
+ return roomData.Type == RoomType.ANGEL and roomData.Subtype == asNumber(nil, AngelRoomSubType.SHOP)
143
+ end
144
+ function ____exports.isBeastRoom(self, roomData)
145
+ return roomData.Type == RoomType.DUNGEON and roomData.Subtype == asNumber(nil, DungeonSubType.BEAST_ROOM)
146
+ end
147
+ --- Helper function to check if the provided room is a boss room for a particular boss. This will
148
+ -- only work for bosses that have dedicated boss rooms in the "00.special rooms.stb" file.
149
+ function ____exports.isBossRoomOf(self, roomData, bossID)
150
+ return roomData.Type == RoomType.BOSS and roomData.StageID == StageID.SPECIAL_ROOMS and roomData.Subtype == asNumber(nil, bossID)
151
+ end
152
+ --- Helper function for determining whether the provided room is a crawl space. Use this function
153
+ -- over comparing to `RoomType.DUNGEON` or `GridRoom.DUNGEON_IDX` since there is a special case of
154
+ -- the player being in a boss fight that takes place in a dungeon.
155
+ function ____exports.isCrawlSpace(self, roomData)
156
+ return roomData.Type == RoomType.DUNGEON and roomData.Subtype == asNumber(nil, DungeonSubType.NORMAL)
157
+ end
158
+ --- Helper function to detect if the provided room is one of the rooms in the Death Certificate area.
159
+ function ____exports.isDeathCertificateArea(self, roomData)
160
+ return roomData.StageID == StageID.HOME and (roomData.Subtype == asNumber(nil, HomeRoomSubType.DEATH_CERTIFICATE_ENTRANCE) or roomData.Subtype == asNumber(nil, HomeRoomSubType.DEATH_CERTIFICATE_ITEMS))
161
+ end
162
+ --- Helper function to detect if the provided room is a Treasure Room created when entering with a
163
+ -- Devil's Crown trinket.
164
+ --
165
+ -- Under the hood, this checks for `RoomDescriptorFlag.DEVIL_TREASURE`.
166
+ function ____exports.isDevilsCrownTreasureRoom(self, roomDescriptor)
167
+ return hasFlag(nil, roomDescriptor.Flags, RoomDescriptorFlag.DEVIL_TREASURE)
168
+ end
169
+ --- Helper function to detect if the provided room is a Double Trouble Boss Room.
170
+ --
171
+ -- This is performed by checking for the string "Double Trouble" inside of the room name. The
172
+ -- vanilla game uses this convention for every Double Trouble Boss Room. Note that this method might
173
+ -- fail for mods that add extra Double Trouble rooms but do not follow the convention.
174
+ --
175
+ -- Internally, the game is coded to detect Double Trouble Boss Rooms by checking for the variant
176
+ -- range of 3700 through 3850. We intentionally do not use this method since it may not work as well
177
+ -- with modded rooms.
178
+ function ____exports.isDoubleTrouble(self, roomData)
179
+ return roomData.Type == RoomType.BOSS and __TS__StringIncludes(roomData.Name, "Double Trouble")
180
+ end
181
+ --- Helper function to determine if the index of the provided room is equal to `GridRoom.GENESIS`.
182
+ function ____exports.isGenesisRoom(self, roomDescriptor)
183
+ return roomDescriptor.GridIndex == asNumber(nil, GridRoom.GENESIS)
184
+ end
185
+ --- Helper function to check if the provided room is either the left Home closet (behind the red
186
+ -- door) or the right Home closet (with one random pickup).
187
+ --
188
+ -- Home closets have a unique shape that is different from any other room in the game.
189
+ function ____exports.isHomeCloset(self, roomData)
190
+ return roomData.StageID == StageID.HOME and (roomData.Subtype == asNumber(nil, HomeRoomSubType.CLOSET_LEFT) or roomData.Subtype == asNumber(nil, HomeRoomSubType.CLOSET_RIGHT))
191
+ end
192
+ --- Helper function to determine if the provided room is one of the four L room shapes.
193
+ function ____exports.isLRoom(self, roomData)
194
+ return isLRoomShape(nil, roomData.Shape)
195
+ end
196
+ --- Helper function to determine if the index of the provided room is equal to `GridRoom.MEGA_SATAN`.
197
+ function ____exports.isMegaSatanRoom(self, roomDescriptor)
198
+ return roomDescriptor.GridIndex == asNumber(nil, GridRoom.MEGA_SATAN)
199
+ end
200
+ --- Helper function to determine if the provided room is part of the Repentance "escape sequence" in
201
+ -- the Mines/Ashpit.
202
+ function ____exports.isMineShaft(self, roomData)
203
+ return (roomData.StageID == StageID.MINES or roomData.StageID == StageID.ASHPIT) and MINE_SHAFT_ROOM_SUB_TYPE_SET:has(roomData.Subtype)
204
+ end
205
+ --- Helper function to check if the provided room is a miniboss room for a particular miniboss. This
206
+ -- will only work for mini-bosses that have dedicated boss rooms in the "00.special rooms.stb" file.
207
+ function ____exports.isMinibossRoomOf(self, roomData, minibossID)
208
+ return roomData.Type == RoomType.MINI_BOSS and roomData.StageID == StageID.SPECIAL_ROOMS and roomData.Subtype == asNumber(nil, minibossID)
209
+ end
210
+ --- Helper function to check if the provided room is a "mirror room" in Downpour or Dross. (These
211
+ -- rooms are marked with a specific sub-type.)
212
+ function ____exports.isMirrorRoom(self, roomData)
213
+ return roomData.Type == RoomType.DEFAULT and (roomData.StageID == StageID.DOWNPOUR or roomData.StageID == StageID.DROSS) and roomData.Subtype == asNumber(nil, DownpourRoomSubType.MIRROR)
214
+ end
215
+ --- Helper function to check if the provided room matches one of the given room types.
216
+ --
217
+ -- This function is variadic, which means you can pass as many room types as you want to match for.
218
+ function ____exports.isRoomType(self, roomData, ...)
219
+ local roomTypes = {...}
220
+ return __TS__ArrayIncludes(roomTypes, roomData.Type)
221
+ end
222
+ --- Helper function for checking if the provided room is a secret exit that leads to a Repentance
223
+ -- floor.
224
+ function ____exports.isSecretExit(self, roomDescriptor)
225
+ return roomDescriptor.GridIndex == asNumber(nil, GridRoom.SECRET_EXIT)
226
+ end
227
+ --- Helper function for checking if the provided room is a secret shop (from the Member Card
228
+ -- collectible).
229
+ --
230
+ -- Secret shops are simply copies of normal shops, but with the backdrop of a secret room. In other
231
+ -- words, they will have the same room type, room variant, and room sub-type of a normal shop. Thus,
232
+ -- the only way to detect them is by using the grid index.
233
+ function ____exports.isSecretShop(self, roomDescriptor)
234
+ return roomDescriptor.GridIndex == asNumber(nil, GridRoom.SECRET_SHOP)
235
+ end
139
236
  local SECRET_ROOM_TYPES = __TS__New(ReadonlySet, {RoomType.SECRET, RoomType.SUPER_SECRET, RoomType.ULTRA_SECRET})
140
237
  --- Helper function for quickly switching to a new room without playing a particular animation. Use
141
238
  -- this helper function over invoking the `Game.ChangeRoom` method directly to ensure that you do
@@ -251,45 +348,34 @@ end
251
348
  --- Helper function to determine if the current room shape is equal to `RoomShape.1x2` or
252
349
  -- `RoomShape.2x1`.
253
350
  function ____exports.in2x1Room(self)
254
- local room = game:GetRoom()
255
- local roomShape = room:GetRoomShape()
256
- return roomShape == RoomShape.SHAPE_1x2 or roomShape == RoomShape.SHAPE_2x1
351
+ local roomData = getRoomData(nil)
352
+ return ____exports.is2x1Room(nil, roomData)
257
353
  end
258
354
  function ____exports.inAngelShop(self)
259
- local room = game:GetRoom()
260
- local roomType = room:GetType()
261
- local roomSubType = getRoomSubType(nil)
262
- return roomType == RoomType.ANGEL and roomSubType == asNumber(nil, AngelRoomSubType.SHOP)
355
+ local roomData = getRoomData(nil)
356
+ return ____exports.isAngelShop(nil, roomData)
263
357
  end
264
358
  function ____exports.inBeastRoom(self)
265
- local room = game:GetRoom()
266
- local roomType = room:GetType()
267
- local roomSubType = getRoomSubType(nil)
268
- return roomType == RoomType.DUNGEON and roomSubType == asNumber(nil, DungeonSubType.BEAST_ROOM)
359
+ local roomData = getRoomData(nil)
360
+ return ____exports.isBeastRoom(nil, roomData)
269
361
  end
270
362
  --- Helper function to check if the current room is a boss room for a particular boss. This will only
271
363
  -- work for bosses that have dedicated boss rooms in the "00.special rooms.stb" file.
272
364
  function ____exports.inBossRoomOf(self, bossID)
273
- local room = game:GetRoom()
274
- local roomType = room:GetType()
275
- local roomStageID = getRoomStageID(nil)
276
- local roomSubType = getRoomSubType(nil)
277
- return roomType == RoomType.BOSS and roomStageID == StageID.SPECIAL_ROOMS and roomSubType == asNumber(nil, bossID)
365
+ local roomData = getRoomData(nil)
366
+ return ____exports.isBossRoomOf(nil, roomData, bossID)
278
367
  end
279
368
  --- Helper function for determining whether the current room is a crawl space. Use this function over
280
369
  -- comparing to `RoomType.DUNGEON` or `GridRoom.DUNGEON_IDX` since there is a special case of the
281
- -- player being in a boss fight that take place in a dungeon.
370
+ -- player being in a boss fight that takes place in a dungeon.
282
371
  function ____exports.inCrawlSpace(self)
283
- local room = game:GetRoom()
284
- local roomType = room:GetType()
285
- local roomSubType = getRoomSubType(nil)
286
- return roomType == RoomType.DUNGEON and roomSubType == asNumber(nil, DungeonSubType.NORMAL)
372
+ local roomData = getRoomData(nil)
373
+ return ____exports.isCrawlSpace(nil, roomData)
287
374
  end
288
375
  --- Helper function to detect if the current room is one of the rooms in the Death Certificate area.
289
376
  function ____exports.inDeathCertificateArea(self)
290
- local roomStageID = getRoomStageID(nil)
291
- local roomSubType = getRoomSubType(nil)
292
- return roomStageID == StageID.HOME and (roomSubType == asNumber(nil, HomeRoomSubType.DEATH_CERTIFICATE_ENTRANCE) or roomSubType == asNumber(nil, HomeRoomSubType.DEATH_CERTIFICATE_ITEMS))
377
+ local roomData = getRoomData(nil)
378
+ return ____exports.isDeathCertificateArea(nil, roomData)
293
379
  end
294
380
  --- Helper function to detect if the current room is a Treasure Room created when entering with a
295
381
  -- Devil's Crown trinket.
@@ -297,83 +383,74 @@ end
297
383
  -- Under the hood, this checks for `RoomDescriptorFlag.DEVIL_TREASURE`.
298
384
  function ____exports.inDevilsCrownTreasureRoom(self)
299
385
  local roomDescriptor = getRoomDescriptorReadOnly(nil)
300
- return hasFlag(nil, roomDescriptor.Flags, RoomDescriptorFlag.DEVIL_TREASURE)
386
+ return ____exports.isDevilsCrownTreasureRoom(nil, roomDescriptor)
301
387
  end
302
388
  --- Helper function to detect if the current room is a Double Trouble Boss Room.
303
389
  --
304
390
  -- This is performed by checking for the string "Double Trouble" inside of the room name. The
305
391
  -- vanilla game uses this convention for every Double Trouble Boss Room. Note that this method might
306
392
  -- fail for mods that add extra Double Trouble rooms but do not follow the convention.
393
+ --
394
+ -- Internally, the game is coded to detect Double Trouble Boss Rooms by checking for the variant
395
+ -- range of 3700 through 3850. We intentionally do not use this method since it may not work as well
396
+ -- with modded rooms.
307
397
  function ____exports.inDoubleTrouble(self)
308
- local room = game:GetRoom()
309
- local roomType = room:GetType()
310
- local roomName = getRoomName(nil)
311
- return roomType == RoomType.BOSS and __TS__StringIncludes(roomName, "Double Trouble")
398
+ local roomData = getRoomData(nil)
399
+ return ____exports.isDoubleTrouble(nil, roomData)
312
400
  end
401
+ --- Helper function to determine if the current room index is equal to `GridRoom.GENESIS`.
313
402
  function ____exports.inGenesisRoom(self)
314
- local roomGridIndex = getRoomGridIndex(nil)
315
- return roomGridIndex == asNumber(nil, GridRoom.GENESIS)
403
+ local roomDescriptor = getRoomDescriptorReadOnly(nil)
404
+ return ____exports.isGenesisRoom(nil, roomDescriptor)
316
405
  end
317
406
  --- Helper function to check if the current room is either the left Home closet (behind the red door)
318
407
  -- or the right Home closet (with one random pickup).
319
408
  --
320
409
  -- Home closets have a unique shape that is different from any other room in the game.
321
410
  function ____exports.inHomeCloset(self)
322
- local level = game:GetLevel()
323
- local stage = level:GetStage()
324
- local roomSubType = getRoomSubType(nil)
325
- return stage == LevelStage.HOME and (roomSubType == asNumber(nil, HomeRoomSubType.CLOSET_LEFT) or roomSubType == asNumber(nil, HomeRoomSubType.CLOSET_RIGHT))
411
+ local roomData = getRoomData(nil)
412
+ return ____exports.isHomeCloset(nil, roomData)
326
413
  end
327
414
  --- Helper function to determine if the current room shape is one of the four L room shapes.
328
415
  function ____exports.inLRoom(self)
329
- local room = game:GetRoom()
330
- local roomShape = room:GetRoomShape()
331
- return roomShape == RoomShape.LTL or roomShape == RoomShape.LTR or roomShape == RoomShape.LBL or roomShape == RoomShape.LBR
416
+ local roomData = getRoomData(nil)
417
+ return ____exports.isLRoom(nil, roomData)
332
418
  end
333
419
  --- Helper function to determine if the current room index is equal to `GridRoom.MEGA_SATAN`.
334
420
  function ____exports.inMegaSatanRoom(self)
335
- local roomGridIndex = getRoomGridIndex(nil)
336
- return roomGridIndex == asNumber(nil, GridRoom.MEGA_SATAN)
421
+ local roomDescriptor = getRoomDescriptorReadOnly(nil)
422
+ return ____exports.isMegaSatanRoom(nil, roomDescriptor)
337
423
  end
338
424
  --- Helper function to determine if the current room is part of the Repentance "escape sequence" in
339
425
  -- the Mines/Ashpit.
340
426
  function ____exports.inMineShaft(self)
341
- local roomStageID = getRoomStageID(nil)
342
- local roomSubType = getRoomSubType(nil)
343
- return (roomStageID == StageID.MINES or roomStageID == StageID.ASHPIT) and MINE_SHAFT_ROOM_SUB_TYPE_SET:has(roomSubType)
427
+ local roomData = getRoomData(nil)
428
+ return ____exports.isMineShaft(nil, roomData)
344
429
  end
345
430
  --- Helper function to check if the current room is a miniboss room for a particular miniboss. This
346
431
  -- will only work for mini-bosses that have dedicated boss rooms in the "00.special rooms.stb" file.
347
432
  function ____exports.inMinibossRoomOf(self, minibossID)
348
- local room = game:GetRoom()
349
- local roomType = room:GetType()
350
- local roomStageID = getRoomStageID(nil)
351
- local roomSubType = getRoomSubType(nil)
352
- return roomType == RoomType.MINI_BOSS and roomStageID == StageID.SPECIAL_ROOMS and roomSubType == asNumber(nil, minibossID)
433
+ local roomData = getRoomData(nil)
434
+ return ____exports.isMinibossRoomOf(nil, roomData, minibossID)
353
435
  end
354
436
  --- Helper function to check if the current room is a "mirror room" in Downpour or Dross. (These
355
437
  -- rooms are marked with a specific sub-type.)
356
438
  function ____exports.inMirrorRoom(self)
357
- local room = game:GetRoom()
358
- local roomType = room:GetType()
359
- local roomStageID = getRoomStageID(nil)
360
- local roomSubType = getRoomSubType(nil)
361
- return roomType == RoomType.DEFAULT and (roomStageID == StageID.DOWNPOUR or roomStageID == StageID.DROSS) and roomSubType == asNumber(nil, DownpourRoomSubType.MIRROR)
439
+ local roomData = getRoomData(nil)
440
+ return ____exports.isMirrorRoom(nil, roomData)
362
441
  end
363
442
  --- Helper function to check if the current room matches one of the given room types.
364
443
  --
365
444
  -- This function is variadic, which means you can pass as many room types as you want to match for.
366
445
  function ____exports.inRoomType(self, ...)
367
- local roomTypes = {...}
368
- local room = game:GetRoom()
369
- local thisRoomType = room:GetType()
370
- return __TS__ArrayIncludes(roomTypes, thisRoomType)
446
+ local roomData = getRoomData(nil)
447
+ return ____exports.isRoomType(nil, roomData, ...)
371
448
  end
372
449
  --- Helper function for checking if the current room is a secret exit that leads to a Repentance
373
450
  -- floor.
374
451
  function ____exports.inSecretExit(self)
375
- local roomGridIndex = getRoomGridIndex(nil)
376
- return roomGridIndex == asNumber(nil, GridRoom.SECRET_EXIT)
452
+ local roomDescriptor = getRoomDescriptorReadOnly(nil)
453
+ return ____exports.isSecretExit(nil, roomDescriptor)
377
454
  end
378
455
  --- Helper function for checking if the current room is a secret shop (from the Member Card
379
456
  -- collectible).
@@ -382,8 +459,8 @@ end
382
459
  -- words, they will have the same room type, room variant, and room sub-type of a normal shop. Thus,
383
460
  -- the only way to detect them is by using the grid index.
384
461
  function ____exports.inSecretShop(self)
385
- local roomGridIndex = getRoomGridIndex(nil)
386
- return roomGridIndex == asNumber(nil, GridRoom.SECRET_SHOP)
462
+ local roomDescriptor = getRoomDescriptorReadOnly(nil)
463
+ return ____exports.isSecretShop(nil, roomDescriptor)
387
464
  end
388
465
  --- Helper function to determine whether or not the current room is the starting room of a floor. It
389
466
  -- only returns true for the starting room of the primary dimension (meaning that being in the
@@ -477,12 +554,12 @@ function ____exports.setRoomCleared(self)
477
554
  for ____, door in ipairs(getDoors(nil)) do
478
555
  do
479
556
  if isHiddenSecretRoomDoor(nil, door) then
480
- goto __continue59
557
+ goto __continue77
481
558
  end
482
559
  openDoorFast(nil, door)
483
560
  door.ExtraVisible = false
484
561
  end
485
- ::__continue59::
562
+ ::__continue77::
486
563
  end
487
564
  sfxManager:Stop(SoundEffect.DOOR_HEAVY_OPEN)
488
565
  game:ShakeScreen(0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isaacscript-common",
3
- "version": "30.12.11",
3
+ "version": "31.0.1",
4
4
  "description": "Helper functions and features for IsaacScript mods.",
5
5
  "keywords": [
6
6
  "isaac",
@@ -25,6 +25,6 @@
25
25
  "main": "dist/src/index",
26
26
  "types": "dist/index.rollup.d.ts",
27
27
  "dependencies": {
28
- "isaac-typescript-definitions": "^13.0.23"
28
+ "isaac-typescript-definitions": "^13.0.24"
29
29
  }
30
30
  }
@@ -99,7 +99,7 @@ export class ItemPoolDetection extends Feature {
99
99
  }
100
100
  }
101
101
 
102
- const [removedItemsMap, removedTrinketsMap] =
102
+ const { removedItemsMap, removedTrinketsMap } =
103
103
  removeItemsAndTrinketsThatAffectItemPools();
104
104
 
105
105
  // Blacklist every collectible in the game except for the provided collectible.
@@ -173,10 +173,10 @@ export class ItemPoolDetection extends Feature {
173
173
  * Before checking the item pools, remove any collectibles or trinkets that would affect the
174
174
  * retrieved collectible types.
175
175
  */
176
- function removeItemsAndTrinketsThatAffectItemPools(): [
177
- removedItemsMap: Map<PlayerIndex, CollectibleType[]>,
178
- removedTrinketsMap: Map<PlayerIndex, TrinketType[]>,
179
- ] {
176
+ function removeItemsAndTrinketsThatAffectItemPools(): {
177
+ removedItemsMap: Map<PlayerIndex, CollectibleType[]>;
178
+ removedTrinketsMap: Map<PlayerIndex, TrinketType[]>;
179
+ } {
180
180
  const removedItemsMap = new Map<PlayerIndex, CollectibleType[]>();
181
181
  const removedTrinketsMap = new Map<PlayerIndex, TrinketType[]>();
182
182
  for (const player of getAllPlayers()) {
@@ -207,7 +207,7 @@ function removeItemsAndTrinketsThatAffectItemPools(): [
207
207
  mapSetPlayer(removedTrinketsMap, player, removedTrinkets);
208
208
  }
209
209
 
210
- return [removedItemsMap, removedTrinketsMap];
210
+ return { removedItemsMap, removedTrinketsMap };
211
211
  }
212
212
 
213
213
  function restoreItemsAndTrinketsThatAffectItemPools(
@@ -14,7 +14,7 @@ import { LadderSubTypeCustom } from "../../../../enums/LadderSubTypeCustom";
14
14
  import { getRandomArrayElement } from "../../../../functions/array";
15
15
  import { spawnEffectWithSeed } from "../../../../functions/entitiesSpecific";
16
16
  import { newRNG } from "../../../../functions/rng";
17
- import { isLRoom, isNarrowRoom } from "../../../../functions/roomShape";
17
+ import { isLRoomShape, isNarrowRoom } from "../../../../functions/roomShape";
18
18
  import {
19
19
  removeCharactersBefore,
20
20
  trimPrefix,
@@ -155,7 +155,7 @@ function spawnWallEntity(
155
155
  );
156
156
  }
157
157
 
158
- if (isLRoom(roomShape)) {
158
+ if (isLRoomShape(roomShape)) {
159
159
  const cornerPNGPath = getBackdropPNGPath(
160
160
  customStage,
161
161
  BackdropKind.CORNER,
@@ -223,7 +223,7 @@ function spawnFloorEntity(customStage: CustomStage, rng: RNG) {
223
223
  );
224
224
  sprite.ReplaceSpritesheet(layerID, wallPNGPath);
225
225
  }
226
- } else if (isLRoom(roomShape)) {
226
+ } else if (isLRoomShape(roomShape)) {
227
227
  for (const layerID of L_FLOOR_ANM2_LAYERS) {
228
228
  const LFloorPNGPath = getBackdropPNGPath(
229
229
  customStage,
@@ -9,7 +9,7 @@ const HEX_STRING_LENGTH = 6;
9
9
  * @param alpha Optional. Range is from 0 to 1. Default is 1. (The same as the `Color` constructor.)
10
10
  */
11
11
  export function hexToColor(hexString: string, alpha = 1): Readonly<Color> {
12
- const [r, g, b] = hexToRGB(hexString);
12
+ const { r, g, b } = hexToRGB(hexString);
13
13
 
14
14
  // Color values should be between 0 and 1.
15
15
  const base = 255;
@@ -23,40 +23,40 @@ export function hexToColor(hexString: string, alpha = 1): Readonly<Color> {
23
23
  * @param alpha Range is from 0 to 1. Default is 1.
24
24
  */
25
25
  export function hexToKColor(hexString: string, alpha = 1): Readonly<KColor> {
26
- const [r, g, b] = hexToRGB(hexString);
26
+ const { r, g, b } = hexToRGB(hexString);
27
27
 
28
28
  // KColor values should be between 0 and 1.
29
29
  const base = 255;
30
30
  return KColor(r / base, g / base, b / base, alpha);
31
31
  }
32
32
 
33
- function hexToRGB(hexString: string): [r: float, g: float, b: float] {
33
+ function hexToRGB(hexString: string): { r: float; g: float; b: float } {
34
34
  hexString = hexString.replace("#", "");
35
35
  if (hexString.length !== HEX_STRING_LENGTH) {
36
36
  logError(`Hex strings must be of length: ${HEX_STRING_LENGTH}`);
37
- return [0, 0, 0];
37
+ return { r: 0, g: 0, b: 0 };
38
38
  }
39
39
 
40
40
  const rString = hexString.slice(0, 2);
41
41
  const r = tonumber(`0x${rString}`);
42
42
  if (r === undefined) {
43
43
  logError(`Failed to convert \`0x${rString}\` to a number.`);
44
- return [0, 0, 0];
44
+ return { r: 0, g: 0, b: 0 };
45
45
  }
46
46
 
47
47
  const gString = hexString.slice(2, 4);
48
48
  const g = tonumber(`0x${gString}`);
49
49
  if (g === undefined) {
50
50
  logError(`Failed to convert \`0x${gString}\` to a number.`);
51
- return [0, 0, 0];
51
+ return { r: 0, g: 0, b: 0 };
52
52
  }
53
53
 
54
54
  const bString = hexString.slice(4, 6);
55
55
  const b = tonumber(`0x${bString}`);
56
56
  if (b === undefined) {
57
57
  logError(`Failed to convert \`0x${bString}\` to a number.`);
58
- return [0, 0, 0];
58
+ return { r: 0, g: 0, b: 0 };
59
59
  }
60
60
 
61
- return [r, g, b];
61
+ return { r, g, b };
62
62
  }
@@ -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;