isaacscript-common 7.4.3 → 7.6.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 (183) hide show
  1. package/dist/callbacks/postGridEntity.d.ts.map +1 -1
  2. package/dist/callbacks/postGridEntity.lua +53 -6
  3. package/dist/callbacks/postPlayerChangeStat.d.ts.map +1 -1
  4. package/dist/callbacks/postPlayerChangeStat.lua +9 -2
  5. package/dist/callbacks/subscriptions/postGridEntityCustomBroken.d.ts +1 -0
  6. package/dist/callbacks/subscriptions/postGridEntityCustomBroken.d.ts.map +1 -1
  7. package/dist/callbacks/subscriptions/postGridEntityCustomBroken.lua +5 -2
  8. package/dist/callbacks/subscriptions/postGridEntityCustomInit.d.ts +9 -0
  9. package/dist/callbacks/subscriptions/postGridEntityCustomInit.d.ts.map +1 -0
  10. package/dist/callbacks/subscriptions/postGridEntityCustomInit.lua +23 -0
  11. package/dist/callbacks/subscriptions/postGridEntityCustomRemove.d.ts +9 -0
  12. package/dist/callbacks/subscriptions/postGridEntityCustomRemove.d.ts.map +1 -0
  13. package/dist/callbacks/subscriptions/postGridEntityCustomRemove.lua +23 -0
  14. package/dist/callbacks/subscriptions/postGridEntityCustomStateChanged.d.ts +9 -0
  15. package/dist/callbacks/subscriptions/postGridEntityCustomStateChanged.d.ts.map +1 -0
  16. package/dist/callbacks/subscriptions/postGridEntityCustomStateChanged.lua +29 -0
  17. package/dist/callbacks/subscriptions/postPlayerChangeStat.d.ts +3 -2
  18. package/dist/callbacks/subscriptions/postPlayerChangeStat.d.ts.map +1 -1
  19. package/dist/classes/DefaultMap.d.ts +3 -2
  20. package/dist/classes/DefaultMap.d.ts.map +1 -1
  21. package/dist/classes/DefaultMap.lua +2 -1
  22. package/dist/enums/ModCallbackCustom.d.ts +131 -81
  23. package/dist/enums/ModCallbackCustom.d.ts.map +1 -1
  24. package/dist/enums/ModCallbackCustom.lua +67 -61
  25. package/dist/enums/StatType.d.ts +3 -1
  26. package/dist/enums/StatType.d.ts.map +1 -1
  27. package/dist/enums/StatType.lua +2 -0
  28. package/dist/features/customGridEntity.d.ts +30 -13
  29. package/dist/features/customGridEntity.d.ts.map +1 -1
  30. package/dist/features/customGridEntity.lua +67 -48
  31. package/dist/features/customStage/customStageGridEntities.d.ts.map +1 -1
  32. package/dist/features/customStage/customStageGridEntities.lua +46 -36
  33. package/dist/features/customStage/customStageUtils.d.ts +2 -1
  34. package/dist/features/customStage/customStageUtils.d.ts.map +1 -1
  35. package/dist/features/customStage/customStageUtils.lua +40 -1
  36. package/dist/features/customStage/exports.d.ts +1 -25
  37. package/dist/features/customStage/exports.d.ts.map +1 -1
  38. package/dist/features/customStage/exports.lua +28 -29
  39. package/dist/features/customStage/v.d.ts +0 -2
  40. package/dist/features/customStage/v.d.ts.map +1 -1
  41. package/dist/features/customStage/v.lua +0 -2
  42. package/dist/features/customStage/versusScreen.d.ts.map +1 -1
  43. package/dist/features/customStage/versusScreen.lua +74 -60
  44. package/dist/features/customTrapdoor/exports.d.ts +28 -16
  45. package/dist/features/customTrapdoor/exports.d.ts.map +1 -1
  46. package/dist/features/customTrapdoor/exports.lua +45 -61
  47. package/dist/features/customTrapdoor/init.d.ts.map +1 -1
  48. package/dist/features/customTrapdoor/init.lua +12 -10
  49. package/dist/features/customTrapdoor/spawn.d.ts +6 -0
  50. package/dist/features/customTrapdoor/spawn.d.ts.map +1 -0
  51. package/dist/features/customTrapdoor/spawn.lua +52 -0
  52. package/dist/features/customTrapdoor/v.d.ts +2 -2
  53. package/dist/features/customTrapdoor/v.d.ts.map +1 -1
  54. package/dist/functions/dev.d.ts +20 -0
  55. package/dist/functions/dev.d.ts.map +1 -0
  56. package/dist/functions/dev.lua +34 -0
  57. package/dist/functions/doors.d.ts +6 -5
  58. package/dist/functions/doors.d.ts.map +1 -1
  59. package/dist/functions/doors.lua +25 -12
  60. package/dist/functions/enums.d.ts +3 -3
  61. package/dist/functions/enums.lua +3 -3
  62. package/dist/functions/playerStats.d.ts.map +1 -1
  63. package/dist/functions/playerStats.lua +2 -1
  64. package/dist/functions/players.d.ts.map +1 -1
  65. package/dist/functions/players.lua +3 -2
  66. package/dist/functions/rooms.d.ts +5 -0
  67. package/dist/functions/rooms.d.ts.map +1 -1
  68. package/dist/functions/rooms.lua +12 -2
  69. package/dist/index.d.ts +172 -11037
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.lua +1134 -8
  72. package/dist/interfaces/{CustomStageLua.d.ts → CustomStageTSConfig.d.ts} +86 -60
  73. package/dist/interfaces/CustomStageTSConfig.d.ts.map +1 -0
  74. package/dist/interfaces/{CustomStageLua.lua → CustomStageTSConfig.lua} +0 -0
  75. package/dist/interfaces/JSONRoomsFile.d.ts +6 -5
  76. package/dist/interfaces/JSONRoomsFile.d.ts.map +1 -1
  77. package/dist/interfaces/StatTypeType.d.ts +1 -0
  78. package/dist/interfaces/StatTypeType.d.ts.map +1 -1
  79. package/dist/interfaces/private/AddCallbackParameterCustom.d.ts +6 -0
  80. package/dist/interfaces/private/AddCallbackParameterCustom.d.ts.map +1 -1
  81. package/dist/interfaces/private/CustomStage.d.ts +1 -1
  82. package/dist/interfaces/private/CustomStage.d.ts.map +1 -1
  83. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts +2 -2
  84. package/dist/interfaces/private/CustomTrapdoorDescription.d.ts.map +1 -1
  85. package/dist/interfaces/private/CustomTrapdoorDestination.d.ts +14 -0
  86. package/dist/interfaces/private/CustomTrapdoorDestination.d.ts.map +1 -0
  87. package/dist/interfaces/{index.lua → private/CustomTrapdoorDestination.lua} +0 -0
  88. package/dist/objects/callbackRegisterFunctions.d.ts.map +1 -1
  89. package/dist/objects/callbackRegisterFunctions.lua +9 -0
  90. package/dist/types/PossibleStatType.d.ts +7 -0
  91. package/dist/types/PossibleStatType.d.ts.map +1 -0
  92. package/dist/types/{TrapdoorDestination.lua → PossibleStatType.lua} +0 -0
  93. package/package.json +1 -1
  94. package/src/callbacks/postGridEntity.ts +75 -10
  95. package/src/callbacks/postPlayerChangeStat.ts +8 -4
  96. package/src/callbacks/subscriptions/postGridEntityCustomBroken.ts +4 -0
  97. package/src/callbacks/subscriptions/postGridEntityCustomInit.ts +38 -0
  98. package/src/callbacks/subscriptions/postGridEntityCustomRemove.ts +35 -0
  99. package/src/callbacks/subscriptions/postGridEntityCustomStateChanged.ts +42 -0
  100. package/src/callbacks/subscriptions/postPlayerChangeStat.ts +4 -7
  101. package/src/classes/DefaultMap.ts +3 -2
  102. package/src/enums/ModCallbackCustom.ts +73 -20
  103. package/src/enums/StatType.ts +3 -3
  104. package/src/features/customGridEntity.ts +87 -61
  105. package/src/features/customStage/customStageGridEntities.ts +36 -23
  106. package/src/features/customStage/customStageUtils.ts +52 -1
  107. package/src/features/customStage/exports.ts +33 -45
  108. package/src/features/customStage/init.ts +1 -1
  109. package/src/features/customStage/v.ts +0 -6
  110. package/src/features/customStage/versusScreen.ts +70 -55
  111. package/src/features/customTrapdoor/exports.ts +60 -66
  112. package/src/features/customTrapdoor/init.ts +12 -11
  113. package/src/features/customTrapdoor/spawn.ts +54 -0
  114. package/src/features/customTrapdoor/v.ts +2 -2
  115. package/src/functions/dev.ts +31 -0
  116. package/src/functions/doors.ts +37 -21
  117. package/src/functions/enums.ts +3 -3
  118. package/src/functions/playerStats.ts +1 -0
  119. package/src/functions/players.ts +7 -3
  120. package/src/functions/rooms.ts +18 -0
  121. package/src/index.ts +207 -9
  122. package/src/interfaces/{CustomStageLua.ts → CustomStageTSConfig.ts} +107 -41
  123. package/src/interfaces/JSONRoomsFile.ts +6 -5
  124. package/src/interfaces/StatTypeType.ts +1 -0
  125. package/src/interfaces/private/AddCallbackParameterCustom.ts +6 -0
  126. package/src/interfaces/private/CustomStage.ts +4 -1
  127. package/src/interfaces/private/CustomTrapdoorDescription.ts +2 -2
  128. package/src/interfaces/private/CustomTrapdoorDestination.ts +14 -0
  129. package/src/objects/callbackRegisterFunctions.ts +9 -0
  130. package/src/types/PossibleStatType.ts +12 -0
  131. package/dist/classes/index.d.ts +0 -3
  132. package/dist/classes/index.d.ts.map +0 -1
  133. package/dist/classes/index.lua +0 -18
  134. package/dist/core/index.d.ts +0 -5
  135. package/dist/core/index.d.ts.map +0 -1
  136. package/dist/core/index.lua +0 -34
  137. package/dist/enums/DecorationVariant.d.ts +0 -10
  138. package/dist/enums/DecorationVariant.d.ts.map +0 -1
  139. package/dist/enums/DecorationVariant.lua +0 -7
  140. package/dist/enums/index.d.ts +0 -11
  141. package/dist/enums/index.d.ts.map +0 -1
  142. package/dist/enums/index.lua +0 -82
  143. package/dist/features/index.d.ts +0 -30
  144. package/dist/features/index.d.ts.map +0 -1
  145. package/dist/features/index.lua +0 -216
  146. package/dist/functions/index.d.ts +0 -100
  147. package/dist/functions/index.d.ts.map +0 -1
  148. package/dist/functions/index.lua +0 -794
  149. package/dist/interfaces/CustomStageLua.d.ts.map +0 -1
  150. package/dist/interfaces/index.d.ts +0 -12
  151. package/dist/interfaces/index.d.ts.map +0 -1
  152. package/dist/maps/index.d.ts +0 -5
  153. package/dist/maps/index.d.ts.map +0 -1
  154. package/dist/maps/index.lua +0 -34
  155. package/dist/objects/index.d.ts +0 -2
  156. package/dist/objects/index.d.ts.map +0 -1
  157. package/dist/objects/index.lua +0 -10
  158. package/dist/types/TrapdoorDestination.d.ts +0 -6
  159. package/dist/types/TrapdoorDestination.d.ts.map +0 -1
  160. package/dist/types/index.d.ts +0 -11
  161. package/dist/types/index.d.ts.map +0 -1
  162. package/dist/types/index.lua +0 -10
  163. package/src/classes/index.ts +0 -2
  164. package/src/classes/indexTypeDoc.ts +0 -2
  165. package/src/core/index.ts +0 -4
  166. package/src/core/indexTypeDoc.ts +0 -4
  167. package/src/enums/DecorationVariant.ts +0 -10
  168. package/src/enums/index.ts +0 -10
  169. package/src/enums/indexTypeDoc.ts +0 -10
  170. package/src/features/index.ts +0 -59
  171. package/src/features/indexTypeDoc.ts +0 -30
  172. package/src/functions/index.ts +0 -101
  173. package/src/functions/indexTypeDoc.ts +0 -101
  174. package/src/indexTypeDoc.ts +0 -13
  175. package/src/interfaces/index.ts +0 -11
  176. package/src/interfaces/indexTypeDoc.ts +0 -11
  177. package/src/maps/index.ts +0 -4
  178. package/src/maps/indexTypeDoc.ts +0 -4
  179. package/src/objects/index.ts +0 -1
  180. package/src/objects/indexTypeDoc.ts +0 -1
  181. package/src/types/TrapdoorDestination.ts +0 -8
  182. package/src/types/index.ts +0 -10
  183. package/src/types/indexTypeDoc.ts +0 -10
@@ -2,7 +2,6 @@ import {
2
2
  ActiveSlot,
3
3
  CollectibleType,
4
4
  DamageFlag,
5
- EntityFlag,
6
5
  EntityType,
7
6
  GridCollisionClass,
8
7
  GridEntityType,
@@ -13,24 +12,24 @@ import { postGridEntityCustomBrokenFire } from "../callbacks/subscriptions/postG
13
12
  import { DefaultMap } from "../classes/DefaultMap";
14
13
  import { ModUpgraded } from "../classes/ModUpgraded";
15
14
  import { game } from "../core/cachedClasses";
16
- import { DecorationVariant } from "../enums/DecorationVariant";
17
15
  import { ModCallbackCustom } from "../enums/ModCallbackCustom";
18
- import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
19
- import { spawn } from "../functions/entities";
16
+ import {
17
+ areFeaturesInitialized,
18
+ errorIfFeaturesNotInitialized,
19
+ } from "../featuresInitialized";
20
20
  import { hasFlag } from "../functions/flag";
21
21
  import {
22
22
  removeGridEntity,
23
23
  spawnGridEntityWithVariant,
24
24
  } from "../functions/gridEntities";
25
25
  import { getRoomListIndex } from "../functions/roomData";
26
- import { asNumber } from "../functions/types";
26
+ import { isNumber } from "../functions/types";
27
27
  import { isVector } from "../functions/vector";
28
28
  import { GridEntityCustomData } from "../interfaces/GridEntityCustomData";
29
29
  import { runNextGameFrame } from "./runInNFrames";
30
30
  import { saveDataManager } from "./saveDataManager/exports";
31
31
 
32
32
  const FEATURE_NAME = "customGridEntity";
33
- const GENERIC_PROP_SIZE_MULTIPLIER = 0.66;
34
33
 
35
34
  const v = {
36
35
  level: {
@@ -200,18 +199,24 @@ function postNewRoomReordered() {
200
199
  * will reappear if the player leaves and re-enters the room. (It will be manually respawned in the
201
200
  * `POST_NEW_ROOM` callback.)
202
201
  *
203
- * This is an IsaacScript feature because the vanilla game does not support any custom grid
204
- * entities. Under the hood, IsaacScript accomplishes this by using decorations with an arbitrary
205
- * non-zero variant to represent custom grid entities.
202
+ * Custom grid entities are built on top of real grid entities. You can use any existing grid entity
203
+ * type as a base. For example, if you want to create a custom rock that would be breakable like a
204
+ * normal rock, then you should specify `GridEntityType.ROCK` as the base grid entity type.
206
205
  *
207
206
  * Once a custom grid entity is spawned, you can take advantage of the custom grid callbacks such as
208
- * `POST_GRID_ENTITY_CUSTOM_UPDATE`.
207
+ * `POST_GRID_ENTITY_CUSTOM_UPDATE`. Note that the "normal" grid entities callbacks will not fire
208
+ * for custom entities. For example, if you had a custom grid entity based on `GridEntityType.ROCK`,
209
+ * and you also had a subscription to the `POST_GRID_ENTITY_UPDATE` callback, the callback would
210
+ * only fire for normal rocks and not the custom entity.
211
+ *
212
+ * Custom grid entities are an IsaacScript feature because the vanilla game does not support any
213
+ * custom grid entities.
209
214
  *
210
215
  * @param gridEntityTypeCustom An integer that identifies what kind of grid entity you are creating.
211
- * It should correspond to a local enum value in your mod. The integer
212
- * can be any unique value and will not correspond to the actual grid
213
- * entity type used. (This integer is used in the various custom grid
214
- * entity callbacks.)
216
+ * It should correspond to a local enum value created in your mod. The
217
+ * integer can be any unique value and will not correspond to the actual
218
+ * grid entity type used. (This integer is used in the various custom
219
+ * grid entity callbacks.)
215
220
  * @param gridIndexOrPosition The grid index or position in the room that you want to spawn the grid
216
221
  * entity at. If a position is specified, the closest grid index will be
217
222
  * used.
@@ -220,10 +225,10 @@ function postNewRoomReordered() {
220
225
  * @param defaultAnimation Optional. The name of the animation to play after the sprite is
221
226
  * initialized and after the player re-enters a room with this grid entity
222
227
  * in it. If not specified, the default animation in the anm2 will be used.
223
- * @param breakable Optional. Whether or not an explosion will be able to break this grid entity.
224
- * False by default. Use the `POST_GRID_ENTITY_CUSTOM_BROKEN` callback to detect
225
- * when it breaks. Due to technical limitations, you can only set the grid entity
226
- * to be breakable if it has a collision class.
228
+ * @param baseGridEntityType Optional. The type of the grid entity to use as a "base" for this
229
+ * custom grid entity. Default is `GridEntityType.DECORATION`.
230
+ * @param baseGridEntityVariant Optional. The variant of the grid entity to use as a "base" for this
231
+ * custom grid entity. Default is 0.
227
232
  */
228
233
  export function spawnCustomGridEntity(
229
234
  gridEntityTypeCustom: GridEntityType,
@@ -231,41 +236,28 @@ export function spawnCustomGridEntity(
231
236
  gridCollisionClass: GridCollisionClass,
232
237
  anm2Path: string,
233
238
  defaultAnimation?: string,
234
- breakable = false,
239
+ baseGridEntityType = GridEntityType.DECORATION,
240
+ baseGridEntityVariant = 0,
235
241
  ): GridEntity {
236
242
  errorIfFeaturesNotInitialized(FEATURE_NAME);
237
243
 
238
- if (breakable && gridCollisionClass === GridCollisionClass.NONE) {
239
- error(
240
- "Failed to spawn a custom grid entity because it is not possible to have breakable custom grid entities with a collision class of: GridCollisionClass.NONE (0)",
241
- );
242
- }
243
-
244
244
  const room = game.GetRoom();
245
245
  const roomListIndex = getRoomListIndex();
246
246
  const gridIndex = isVector(gridIndexOrPosition)
247
247
  ? room.GetGridIndex(gridIndexOrPosition)
248
248
  : gridIndexOrPosition;
249
249
 
250
- const existingGridEntity = room.GetGridEntity(gridIndex);
251
- const isExistingDecoration =
252
- existingGridEntity !== undefined &&
253
- existingGridEntity.GetType() === GridEntityType.DECORATION &&
254
- existingGridEntity.GetVariant() ===
255
- asNumber(DecorationVariant.CUSTOM_GRID_ENTITY);
256
- const decoration = isExistingDecoration
257
- ? existingGridEntity
258
- : spawnGridEntityWithVariant(
259
- GridEntityType.DECORATION,
260
- DecorationVariant.CUSTOM_GRID_ENTITY,
261
- gridIndexOrPosition,
262
- );
263
- if (decoration === undefined) {
264
- error("Failed to spawn a decoration for a custom grid entity.");
250
+ const customGridEntity = spawnGridEntityWithVariant(
251
+ baseGridEntityType,
252
+ baseGridEntityVariant,
253
+ gridIndexOrPosition,
254
+ );
255
+ if (customGridEntity === undefined) {
256
+ error("Failed to spawn a custom grid entity.");
265
257
  }
266
- decoration.CollisionClass = gridCollisionClass;
258
+ customGridEntity.CollisionClass = gridCollisionClass;
267
259
 
268
- const sprite = decoration.GetSprite();
260
+ const sprite = customGridEntity.GetSprite();
269
261
  sprite.Load(anm2Path, true);
270
262
  const animationToPlay =
271
263
  defaultAnimation === undefined
@@ -286,25 +278,7 @@ export function spawnCustomGridEntity(
286
278
  v.level.customGridEntities.getAndSetDefault(roomListIndex);
287
279
  roomCustomGridEntities.set(gridIndex, customGridEntityData);
288
280
 
289
- // We check to see if an explosion touches a custom grid entity by spawning a Dummy on top, and
290
- // the monitoring for explosions in the `ENTITY_TAKE_DMG` callback.
291
- if (breakable) {
292
- const position = room.GetGridPosition(gridIndex);
293
- const entity = spawn(EntityType.GENERIC_PROP, 0, 0, position);
294
- entity.ClearEntityFlags(EntityFlag.APPEAR);
295
- entity.Visible = false;
296
-
297
- // By default, it is larger than a grid tile, so make it a bit smaller.
298
- entity.SizeMulti = Vector(
299
- GENERIC_PROP_SIZE_MULTIPLIER,
300
- GENERIC_PROP_SIZE_MULTIPLIER,
301
- );
302
-
303
- const ptrHash = GetPtrHash(entity);
304
- v.room.genericPropPtrHashes.add(ptrHash);
305
- }
306
-
307
- return decoration;
281
+ return customGridEntity;
308
282
  }
309
283
 
310
284
  /**
@@ -326,6 +300,8 @@ export function removeCustomGridEntity(
326
300
  gridIndexOrPositionOrGridEntity: int | Vector | GridEntity,
327
301
  updateRoom = true,
328
302
  ): GridEntity | undefined {
303
+ errorIfFeaturesNotInitialized(FEATURE_NAME);
304
+
329
305
  const room = game.GetRoom();
330
306
  const roomListIndex = getRoomListIndex();
331
307
 
@@ -371,6 +347,10 @@ export function removeCustomGridEntity(
371
347
  export function getCustomGridEntities(): Array<
372
348
  [gridEntity: GridEntity, data: GridEntityCustomData]
373
349
  > {
350
+ if (!areFeaturesInitialized()) {
351
+ return [];
352
+ }
353
+
374
354
  const roomListIndex = getRoomListIndex();
375
355
  const roomCustomGridEntities = v.level.customGridEntities.get(roomListIndex);
376
356
  if (roomCustomGridEntities === undefined) {
@@ -388,3 +368,49 @@ export function getCustomGridEntities(): Array<
388
368
 
389
369
  return customGridEntities;
390
370
  }
371
+
372
+ /**
373
+ * Helper function to get the custom `GridEntityType` from a `GridEntity` or grid index. Returns
374
+ * undefined if the provided `GridEntity` is not a custom grid entity, or if there was not a grid
375
+ * entity on the provided grid index.
376
+ */
377
+ export function getCustomGridEntityType(
378
+ gridEntityOrGridIndex: GridEntity | int,
379
+ ): GridEntityType | undefined {
380
+ if (!areFeaturesInitialized()) {
381
+ return undefined;
382
+ }
383
+
384
+ const gridIndex = isNumber(gridEntityOrGridIndex)
385
+ ? gridEntityOrGridIndex
386
+ : gridEntityOrGridIndex.GetGridIndex();
387
+
388
+ const roomListIndex = getRoomListIndex();
389
+ const roomCustomGridEntities = v.level.customGridEntities.get(roomListIndex);
390
+ if (roomCustomGridEntities === undefined) {
391
+ return undefined;
392
+ }
393
+
394
+ for (const [_gridIndex, data] of roomCustomGridEntities.entries()) {
395
+ if (data.gridIndex === gridIndex) {
396
+ return data.gridEntityTypeCustom;
397
+ }
398
+ }
399
+
400
+ return undefined;
401
+ }
402
+
403
+ /**
404
+ * Helper function to check if a `GridEntity` is a custom grid entity or if a grid index has a
405
+ * custom grid entity.
406
+ */
407
+ export function isCustomGridEntity(
408
+ gridEntityOrGridIndex: GridEntity | int,
409
+ ): boolean {
410
+ if (!areFeaturesInitialized()) {
411
+ return false;
412
+ }
413
+
414
+ const gridEntityTypeCustom = getCustomGridEntityType(gridEntityOrGridIndex);
415
+ return gridEntityTypeCustom !== undefined;
416
+ }
@@ -5,7 +5,6 @@ import {
5
5
  LevelStage,
6
6
  TrinketType,
7
7
  } from "isaac-typescript-definitions";
8
- import { DecorationVariant } from "../../enums/DecorationVariant";
9
8
  import { removeEntities } from "../../functions/entities";
10
9
  import { getNPCs } from "../../functions/entitiesSpecific";
11
10
  import { removeGridEntity } from "../../functions/gridEntities";
@@ -15,11 +14,13 @@ import {
15
14
  getTrinkets,
16
15
  } from "../../functions/pickupsSpecific";
17
16
  import { calculateStageType } from "../../functions/stage";
18
- import { asNumber } from "../../functions/types";
19
17
  import { vectorEquals } from "../../functions/vector";
20
18
  import { CustomStage } from "../../interfaces/private/CustomStage";
21
- import { TrapdoorDestination } from "../../types/TrapdoorDestination";
22
- import { spawnCustomTrapdoor } from "../customTrapdoor/exports";
19
+ import { isCustomGridEntity } from "../customGridEntity";
20
+ import {
21
+ spawnCustomTrapdoor,
22
+ spawnCustomTrapdoorToVanilla,
23
+ } from "../customTrapdoor/exports";
23
24
  import { DEFAULT_BASE_STAGE } from "./exports";
24
25
  import v from "./v";
25
26
 
@@ -34,14 +35,12 @@ export function setCustomDecorationGraphics(
34
35
  return;
35
36
  }
36
37
 
37
- const gridEntityType = gridEntity.GetType();
38
- if (gridEntityType !== GridEntityType.DECORATION) {
38
+ if (isCustomGridEntity(gridEntity)) {
39
39
  return;
40
40
  }
41
41
 
42
- // Ignore custom grid entities. (They are represented as decorations with a non-zero variant.)
43
- const variant = gridEntity.GetVariant();
44
- if (variant !== asNumber(DecorationVariant.VANILLA_DECORATION)) {
42
+ const gridEntityType = gridEntity.GetType();
43
+ if (gridEntityType !== GridEntityType.DECORATION) {
45
44
  return;
46
45
  }
47
46
 
@@ -65,6 +64,10 @@ export function setCustomRockGraphics(
65
64
  return;
66
65
  }
67
66
 
67
+ if (isCustomGridEntity(gridEntity)) {
68
+ return;
69
+ }
70
+
68
71
  const gridEntityRock = gridEntity.ToRock();
69
72
  if (gridEntityRock === undefined) {
70
73
  return;
@@ -92,6 +95,10 @@ export function setCustomPitGraphics(
92
95
  return;
93
96
  }
94
97
 
98
+ if (isCustomGridEntity(gridEntity)) {
99
+ return;
100
+ }
101
+
95
102
  const gridEntityPit = gridEntity.ToPit();
96
103
  if (gridEntityPit === undefined) {
97
104
  return;
@@ -116,6 +123,10 @@ export function setCustomDoorGraphics(
116
123
  return;
117
124
  }
118
125
 
126
+ if (isCustomGridEntity(gridEntity)) {
127
+ return;
128
+ }
129
+
119
130
  const gridEntityDoor = gridEntity.ToDoor();
120
131
  if (gridEntityDoor === undefined) {
121
132
  return;
@@ -194,20 +205,22 @@ export function convertVanillaTrapdoors(
194
205
 
195
206
  removeGridEntity(gridEntity, true);
196
207
 
197
- // - If we are on the first floor of a custom stage, then the destination will be the second floor
198
- // of the custom stage. (e.g. Caves 1 to Caves 2)
199
- // - If we are on the second floor of a custom stage, then the destination will be the vanilla
200
- // floor equivalent to 2 floors after the floor used as a basis for the custom stage.
201
- const baseStage =
202
- customStage.baseStage === undefined
203
- ? DEFAULT_BASE_STAGE
204
- : customStage.baseStage;
205
- const vanillaNextStage = (baseStage + 2) as LevelStage;
206
- const vanillaNextStageType = calculateStageType(vanillaNextStage);
207
- const destination: TrapdoorDestination = v.run.firstFloor
208
- ? [customStage.name, 2]
209
- : [vanillaNextStage, vanillaNextStageType];
210
- spawnCustomTrapdoor(gridEntity.Position, destination);
208
+ if (v.run.firstFloor) {
209
+ // If we are on the first floor of a custom stage, then the destination will be the second floor
210
+ // of the custom stage. (e.g. Caves 1 to Caves 2)
211
+ spawnCustomTrapdoor(gridEntity.Position, customStage.name, 2);
212
+ } else {
213
+ // If we are on the second floor of a custom stage, then the destination will be the vanilla
214
+ // floor equivalent to 2 floors after the floor used as a basis for the custom stage.
215
+ const baseStage =
216
+ customStage.baseStage === undefined
217
+ ? DEFAULT_BASE_STAGE
218
+ : customStage.baseStage;
219
+ const stage = (baseStage + 2) as LevelStage;
220
+ const stageType = calculateStageType(stage);
221
+
222
+ spawnCustomTrapdoorToVanilla(gridEntity.Position, stage, stageType);
223
+ }
211
224
  }
212
225
 
213
226
  /**
@@ -2,7 +2,10 @@ import { sumArray } from "../../functions/array";
2
2
  import { log } from "../../functions/log";
3
3
  import { getRandomFloat } from "../../functions/random";
4
4
  import { getRandomSeed } from "../../functions/rng";
5
- import { CustomStageRoomMetadata } from "../../interfaces/CustomStageLua";
5
+ import {
6
+ CustomStageBossPoolEntry,
7
+ CustomStageRoomMetadata,
8
+ } from "../../interfaces/CustomStageTSConfig";
6
9
 
7
10
  /**
8
11
  * Helper function to get a random custom stage room from an array of custom stage rooms.
@@ -52,3 +55,51 @@ function getCustomStageRoomWithChosenWeight(
52
55
  `Failed to get a custom stage room with chosen weight: ${chosenWeight}`,
53
56
  );
54
57
  }
58
+
59
+ export function getRandomBossRoomFromPool(
60
+ roomsMetadata: readonly CustomStageRoomMetadata[],
61
+ bossPool: readonly CustomStageBossPoolEntry[],
62
+ seedOrRNG: Seed | RNG = getRandomSeed(),
63
+ verbose = false,
64
+ ): CustomStageRoomMetadata {
65
+ const totalWeight = getTotalWeightOfBossPool(bossPool);
66
+ if (verbose) {
67
+ log(`Total weight of the custom stage boss pool provided: ${totalWeight}`);
68
+ }
69
+
70
+ const chosenWeight = getRandomFloat(0, totalWeight, seedOrRNG);
71
+ if (verbose) {
72
+ log(`Randomly chose weight for custom stage boss pool: ${chosenWeight}`);
73
+ }
74
+
75
+ const bossEntry = getBossEntryWithChosenWeight(bossPool, chosenWeight);
76
+
77
+ const roomsMetadataForBoss = roomsMetadata.filter(
78
+ (roomMetadata) => roomMetadata.subType === bossEntry.subType,
79
+ );
80
+ return getRandomCustomStageRoom(roomsMetadataForBoss, seedOrRNG, verbose);
81
+ }
82
+
83
+ function getTotalWeightOfBossPool(
84
+ bossPool: readonly CustomStageBossPoolEntry[],
85
+ ): float {
86
+ const weights = bossPool.map((bossEntry) => bossEntry.weight);
87
+ return sumArray(weights);
88
+ }
89
+
90
+ function getBossEntryWithChosenWeight(
91
+ bossPool: readonly CustomStageBossPoolEntry[],
92
+ chosenWeight: float,
93
+ ): CustomStageBossPoolEntry {
94
+ for (const bossEntry of bossPool) {
95
+ if (chosenWeight < bossEntry.weight) {
96
+ return bossEntry;
97
+ }
98
+
99
+ chosenWeight -= bossEntry.weight;
100
+ }
101
+
102
+ error(
103
+ `Failed to get a custom stage boss entry with chosen weight: ${chosenWeight}`,
104
+ );
105
+ }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import {
11
- EntityType,
11
+ DoorSlot,
12
12
  LevelStage,
13
13
  RoomShape,
14
14
  RoomType,
@@ -16,7 +16,7 @@ import {
16
16
  } from "isaac-typescript-definitions";
17
17
  import { reorderedCallbacksSetStageInternal } from "../../callbacks/reorderedCallbacks";
18
18
  import { game } from "../../core/cachedClasses";
19
- import { getEntityIDFromConstituents } from "../../functions/entities";
19
+ import { doorSlotFlagsToDoorSlots } from "../../functions/doors";
20
20
  import { logError } from "../../functions/log";
21
21
  import { newRNG } from "../../functions/rng";
22
22
  import {
@@ -25,13 +25,13 @@ import {
25
25
  } from "../../functions/rooms";
26
26
  import { setStage } from "../../functions/stage";
27
27
  import { asNumber } from "../../functions/types";
28
+ import { CustomStageRoomMetadata } from "../../interfaces/CustomStageTSConfig";
28
29
  import { CustomStage } from "../../interfaces/private/CustomStage";
29
- import { getRandomCustomStageRoom } from "./customStageUtils";
30
- import v, {
31
- customBossPNGPaths,
32
- customStageCachedRoomData,
33
- customStagesMap,
34
- } from "./v";
30
+ import {
31
+ getRandomBossRoomFromPool,
32
+ getRandomCustomStageRoom,
33
+ } from "./customStageUtils";
34
+ import v, { customStageCachedRoomData, customStagesMap } from "./v";
35
35
 
36
36
  export const DEFAULT_BASE_STAGE = LevelStage.BASEMENT_2;
37
37
  export const DEFAULT_BASE_STAGE_TYPE = StageType.ORIGINAL;
@@ -135,8 +135,8 @@ function setStageRoomsData(
135
135
  const roomType = room.Data.Type;
136
136
  const roomShapeMap = customStage.roomTypeMap.get(roomType);
137
137
  if (roomShapeMap === undefined) {
138
- // Only show errors for non-default room types. (By default, we won't replace shops and other
139
- // special rooms.)
138
+ // Only show errors for non-default room types. (We do not require that end-users provide
139
+ // custom rooms for shops and other special rooms inside of their XML file.)
140
140
  if (roomType === RoomType.DEFAULT) {
141
141
  logError(
142
142
  `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) for custom stage: ${customStage.name}`,
@@ -160,10 +160,32 @@ function setStageRoomsData(
160
160
  logError(
161
161
  `Failed to find any custom rooms for RoomType.${RoomType[roomType]} (${roomType}) + RoomShape.${RoomShape[roomShape]} (${roomShape}) + DoorSlotFlags ${doorSlotFlags} for custom stage: ${customStage.name}`,
162
162
  );
163
+
164
+ const header = `For reference, a DoorSlotFlags of ${doorSlotFlags} is equal to the following doors being enabled:\n`;
165
+ const doorSlots = doorSlotFlagsToDoorSlots(doorSlotFlags);
166
+ const doorSlotLines = doorSlots.map(
167
+ (doorSlot) => `- DoorSlot.${DoorSlot[doorSlot]} (${doorSlot})`,
168
+ );
169
+ const explanation = header + doorSlotLines.join("\n");
170
+ logError(explanation);
163
171
  continue;
164
172
  }
165
173
 
166
- const randomRoom = getRandomCustomStageRoom(roomsMetadata, rng, verbose);
174
+ let randomRoom: CustomStageRoomMetadata;
175
+ if (roomType === RoomType.BOSS) {
176
+ if (customStage.bossPool === undefined) {
177
+ continue;
178
+ }
179
+
180
+ randomRoom = getRandomBossRoomFromPool(
181
+ roomsMetadata,
182
+ customStage.bossPool,
183
+ rng,
184
+ verbose,
185
+ );
186
+ } else {
187
+ randomRoom = getRandomCustomStageRoom(roomsMetadata, rng, verbose);
188
+ }
167
189
 
168
190
  let newRoomData = customStageCachedRoomData.get(randomRoom.variant);
169
191
  if (newRoomData === undefined) {
@@ -187,40 +209,6 @@ function setStageRoomsData(
187
209
  }
188
210
  }
189
211
 
190
- /**
191
- * By default, unknown bosses will be drawn on the boss "versus" screen as "???". If your custom
192
- * stage has custom bosses, you can use this function to register the corresponding graphic file
193
- * files for them.
194
- *
195
- * For reference:
196
- * - The vanilla name sprite for Monstro is located at:
197
- * `resources/gfx/ui/boss/bossname_20.0_monstro.png`
198
- * - The vanilla portrait sprite for Monstro is located at:
199
- * `resources/gfx/ui/boss/portrait_20.0_monstro.png`
200
- *
201
- * (Note that boss metadata like this cannot be specified with the rest of the custom stage metadata
202
- * in the "tsconfig.json" file because there is not a way to retrieve the name of an entity at
203
- * run-time.)
204
- *
205
- * @param entityType The entity type of the custom boss.
206
- * @param variant The variant of the custom boss.
207
- * @param subType The sub-type of the custom boss.
208
- * @param namePNGPath The full path to the PNG file that contains the name of the boss that will be
209
- * displayed on the top of the boss "versus" screen.
210
- * @param portraitPNGPath The full path to the PNG file that contains the portrait of the boss that
211
- * will be displayed on the right side of the boss "versus" screen.
212
- */
213
- export function registerCustomBoss(
214
- entityType: EntityType,
215
- variant: int,
216
- subType: int,
217
- namePNGPath: string,
218
- portraitPNGPath: string,
219
- ): void {
220
- const entityID = getEntityIDFromConstituents(entityType, variant, subType);
221
- customBossPNGPaths.set(entityID, [namePNGPath, portraitPNGPath]);
222
- }
223
-
224
212
  /**
225
213
  * Helper function to disable the custom stage. This is typically called before taking the player to
226
214
  * a vanilla floor.
@@ -13,7 +13,7 @@ import { hasFlag, removeFlag } from "../../functions/flag";
13
13
  import {
14
14
  CustomStageLua,
15
15
  CustomStageRoomMetadata,
16
- } from "../../interfaces/CustomStageLua";
16
+ } from "../../interfaces/CustomStageTSConfig";
17
17
  import { CustomStage, RoomTypeMap } from "../../interfaces/private/CustomStage";
18
18
  import { saveDataManager } from "../saveDataManager/exports";
19
19
  import { setCustomStageBackdrop } from "./backdrop";
@@ -40,9 +40,3 @@ export const customStagesMap = new Map<string, CustomStage>();
40
40
 
41
41
  /** Indexed by room variant. */
42
42
  export const customStageCachedRoomData = new Map<int, Readonly<RoomConfig>>();
43
-
44
- /** Indexed by entity ID. */
45
- export const customBossPNGPaths = new Map<
46
- string,
47
- [namePNGPath: string, portraitPNGPath: string]
48
- >();