isaacscript-common 7.10.0 → 7.13.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 (38) hide show
  1. package/dist/features/customPickup.d.ts +24 -0
  2. package/dist/features/customPickup.d.ts.map +1 -0
  3. package/dist/features/customPickup.lua +85 -0
  4. package/dist/features/customStage/backdrop.lua +6 -4
  5. package/dist/features/customStage/customStageGridEntities.d.ts.map +1 -1
  6. package/dist/features/customStage/customStageGridEntities.lua +68 -36
  7. package/dist/features/customStage/shadows.d.ts.map +1 -1
  8. package/dist/features/customStage/shadows.lua +6 -3
  9. package/dist/features/customStage/versusScreen.d.ts.map +1 -1
  10. package/dist/features/customStage/versusScreen.lua +6 -2
  11. package/dist/functions/npcs.lua +3 -3
  12. package/dist/functions/pickups.lua +2 -2
  13. package/dist/functions/string.d.ts +5 -0
  14. package/dist/functions/string.d.ts.map +1 -1
  15. package/dist/functions/string.lua +7 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.lua +5 -0
  19. package/dist/initFeatures.d.ts.map +1 -1
  20. package/dist/initFeatures.lua +3 -0
  21. package/dist/interfaces/CustomStageTSConfig.d.ts +47 -0
  22. package/dist/interfaces/CustomStageTSConfig.d.ts.map +1 -1
  23. package/dist/objects/coinSubTypeToValue.d.ts +1 -1
  24. package/dist/objects/coinSubTypeToValue.d.ts.map +1 -1
  25. package/dist/objects/coinSubTypeToValue.lua +1 -1
  26. package/package.json +1 -1
  27. package/src/features/customPickup.ts +135 -0
  28. package/src/features/customStage/backdrop.ts +5 -4
  29. package/src/features/customStage/customStageGridEntities.ts +65 -12
  30. package/src/features/customStage/shadows.ts +5 -3
  31. package/src/features/customStage/versusScreen.ts +11 -2
  32. package/src/functions/npcs.ts +2 -2
  33. package/src/functions/pickups.ts +2 -2
  34. package/src/functions/string.ts +12 -0
  35. package/src/index.ts +1 -0
  36. package/src/initFeatures.ts +2 -0
  37. package/src/interfaces/CustomStageTSConfig.ts +50 -0
  38. package/src/objects/coinSubTypeToValue.ts +1 -1
@@ -119,8 +119,22 @@ export interface CustomStageTSConfig {
119
119
  *
120
120
  * If not specified, the vanilla Basement decorations spritesheet will be used. For reference,
121
121
  * this is located at: `resources/gfx/grid/props_01_basement.png`
122
+ *
123
+ * If you want to have custom animations for your decorations, then do not use this field and use
124
+ * `decorationsANM2Path` instead.
122
125
  */
123
126
  decorationsPNGPath?: string;
127
+ /**
128
+ * Optional. The full path to the anm2 file that contains the custom animations for the
129
+ * decorations of the floor.
130
+ *
131
+ * If not specified, the vanilla Basement decorations spritesheet will be used. For reference,
132
+ * this is located at: `resources/gfx/grid/props_01_basement.png`
133
+ *
134
+ * If you do not want to have custom animations for your decorations, then do not use this field
135
+ * and use `decorationsPNGPath` instead.
136
+ */
137
+ decorationsANM2Path?: string;
124
138
  /**
125
139
  * Optional. The full path to the spritesheet that contains the graphics of the rocks/blocks/urns
126
140
  * for the floor.
@@ -132,16 +146,49 @@ export interface CustomStageTSConfig {
132
146
  *
133
147
  * If not specified, the vanilla Basement rocks spritesheet will be used. For reference, this is
134
148
  * located at: `resources-dlc3/gfx/grid/rocks_basement.png`
149
+ *
150
+ * If you want to have custom animations for your rocks, then do not use this field and use
151
+ * `rocksANM2Path` instead.
135
152
  */
136
153
  rocksPNGPath?: string;
154
+ /**
155
+ * Optional. The full path to the anm2 file that contains the custom animations for the
156
+ * rocks/blocks/urns of the floor.
157
+ *
158
+ * If specified, it is assumed that you have your own custom rock alt type, and all vanilla
159
+ * rewards/enemies that spawn from urns will be automatically removed. Use the
160
+ * `POST_GRID_ENTITY_BROKEN` callback to make your own custom rewards. Or, if you want to emulate
161
+ * a vanilla urn/mushroom/skull/polyp/bucket, use the `spawnRockAltReward` helper function.
162
+ *
163
+ * If not specified, the vanilla Basement rocks spritesheet will be used. For reference, this is
164
+ * located at: `resources-dlc3/gfx/grid/rocks_basement.png`
165
+ *
166
+ * If you do not want to have custom animations for your rocks, then do not use this field and use
167
+ * `rocksPNGPath` instead.
168
+ */
169
+ rocksANM2Path?: string;
137
170
  /**
138
171
  * Optional. The full path to the spritesheet that contains the graphics of the pits for the
139
172
  * floor.
140
173
  *
141
174
  * If not specified, the vanilla Basement pits spritesheet will be used. For reference, this is
142
175
  * located at: `resources/gfx/grid/grid_pit.png`
176
+ *
177
+ * If you do not want to have custom animations for your pits, then do not use this field and use
178
+ * `pitsANM2Path` instead.
143
179
  */
144
180
  pitsPNGPath?: string;
181
+ /**
182
+ * Optional. The full path to the anm2 file that contains the custom animations for the pits of
183
+ * the floor.
184
+ *
185
+ * If not specified, the vanilla Basement pits spritesheet will be used. For reference, this is
186
+ * located at: `resources/gfx/grid/grid_pit.png`
187
+ *
188
+ * If you do not want to have custom animations for your pits, then do not use this field and use
189
+ * `pitsPNGPath` instead.
190
+ */
191
+ pitsANM2Path?: string;
145
192
  /**
146
193
  * Optional. A collection of paths that contain graphics for the doors of the floor. If not
147
194
  * specified, the doors for Basement will be used.
@@ -1 +1 @@
1
- {"version":3,"file":"CustomStageTSConfig.d.ts","sourceRoot":"","sources":["../../src/interfaces/CustomStageTSConfig.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CustomStageTSConfig.d.ts","sourceRoot":"","sources":["../../src/interfaces/CustomStageTSConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;;;;;;OAUG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE;QACjB;;;;;;;;WAQG;QACH,OAAO,EAAE,MAAM,EAAE,CAAC;QAElB;;;;;;;;WAQG;QACH,OAAO,EAAE,MAAM,EAAE,CAAC;QAElB;;;;;;;;;;WAUG;QACH,KAAK,EAAE,MAAM,EAAE,CAAC;QAEhB;;;;;;;;;;;;WAYG;QACH,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IAEF;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;;;;;;OASG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,YAAY,CAAC,EAAE;QACb;;;;;;WAMG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAEhB;;;;;;WAMG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;QAEtB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAElB;;;;;;WAMG;QACH,UAAU,CAAC,EAAE,MAAM,CAAC;QAEpB;;;;;;WAMG;QACH,MAAM,CAAC,EAAE,MAAM,CAAC;QAEhB;;;;;;WAMG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;QAEnB;;;;;;WAMG;QACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAE7B;;;;;;WAMG;QACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAE3B;;;;;;WAMG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;QAEnB;;;;;;WAMG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;QAEnB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAElB;;;;;;WAMG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF;;;;;OAKG;IACH,OAAO,CAAC,EAAE;QACR;;;;;;;WAOG;QACH,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;QAE5B;;;;;;;WAOG;QACH,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;QAE5B;;;;;;;WAOG;QACH,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;QAE5B;;;;;;;;WAQG;QACH,KAAK,CAAC,EAAE,iBAAiB,EAAE,CAAC;KAC7B,CAAC;IAEF;;;OAGG;IACH,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAC;IAEtC;;;;;OAKG;IACH,YAAY,CAAC,EAAE;QACb;;;;;;WAMG;QACH,eAAe,CAAC,EAAE;YAChB;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;YAEV;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;YAEV;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;YAEV;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;SACX,CAAC;QAEF;;;;;;;WAOG;QACH,aAAa,CAAC,EAAE;YACd;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;YAEV;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;YAEV;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;YAEV;;;eAGG;YACH,CAAC,EAAE,MAAM,CAAC;SACX,CAAC;KACH,CAAC;CACH;AAED;;;GAGG;AAEH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;OAKG;IACH,KAAK,CAAC,EAAE;QACN;;;WAGG;QACH,CAAC,EAAE,MAAM,CAAC;QAEV;;;WAGG;QACH,CAAC,EAAE,MAAM,CAAC;QAEV;;;WAGG;QACH,CAAC,EAAE,MAAM,CAAC;QAEV;;;WAGG;QACH,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;CACH;AAED;;;GAGG;AAEH,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;;;;;;;;;;OAeG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf,kFAAkF;IAClF,YAAY,CAAC,EAAE;QAEb;;;;;WAKG;QACH,WAAW,EAAE,MAAM,CAAC;QAGpB;;;;;WAKG;QACH,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH"}
@@ -1,6 +1,6 @@
1
1
  import { CoinSubType } from "isaac-typescript-definitions";
2
2
  export declare const DEFAULT_COIN_VALUE = 1;
3
- export declare const COIN_SUBTYPE_TO_VALUE: {
3
+ export declare const COIN_SUB_TYPE_TO_VALUE: {
4
4
  readonly [key in CoinSubType]: int;
5
5
  };
6
6
  //# sourceMappingURL=coinSubTypeToValue.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"coinSubTypeToValue.d.ts","sourceRoot":"","sources":["../../src/objects/coinSubTypeToValue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,eAAO,MAAM,qBAAqB,EAAE;IAAE,QAAQ,EAAE,GAAG,IAAI,WAAW,GAAG,GAAG;CAS9D,CAAC"}
1
+ {"version":3,"file":"coinSubTypeToValue.d.ts","sourceRoot":"","sources":["../../src/objects/coinSubTypeToValue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAE3D,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,eAAO,MAAM,sBAAsB,EAAE;IAAE,QAAQ,EAAE,GAAG,IAAI,WAAW,GAAG,GAAG;CAS/D,CAAC"}
@@ -2,7 +2,7 @@ local ____exports = {}
2
2
  local ____isaac_2Dtypescript_2Ddefinitions = require("isaac-typescript-definitions")
3
3
  local CoinSubType = ____isaac_2Dtypescript_2Ddefinitions.CoinSubType
4
4
  ____exports.DEFAULT_COIN_VALUE = 1
5
- ____exports.COIN_SUBTYPE_TO_VALUE = {
5
+ ____exports.COIN_SUB_TYPE_TO_VALUE = {
6
6
  [CoinSubType.NULL] = 0,
7
7
  [CoinSubType.PENNY] = 1,
8
8
  [CoinSubType.NICKEL] = 5,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isaacscript-common",
3
- "version": "7.10.0",
3
+ "version": "7.13.0",
4
4
  "description": "Helper functions and features for IsaacScript mods.",
5
5
  "keywords": [
6
6
  "isaac",
@@ -0,0 +1,135 @@
1
+ import {
2
+ EffectVariant,
3
+ EntityType,
4
+ ModCallback,
5
+ PickupVariant,
6
+ } from "isaac-typescript-definitions";
7
+ import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
8
+ import {
9
+ getEntityID,
10
+ getEntityIDFromConstituents,
11
+ } from "../functions/entities";
12
+ import { spawnEffect } from "../functions/entitiesSpecific";
13
+
14
+ const FEATURE_NAME = "customPickup";
15
+
16
+ interface CustomPickupFunctions {
17
+ collectFunc: (player: EntityPlayer) => void;
18
+ collisionFunc: (player: EntityPlayer) => boolean;
19
+ }
20
+
21
+ /**
22
+ * Normally, we would make a custom entity to represent a fading-away pickup, but we don't want to
23
+ * interfere with the "entities2.xml" file in end-user mods. Thus, we must select a vanilla effect
24
+ * to masquerade as a backdrop effect.
25
+ *
26
+ * We arbitrarily choose a ladder for this purpose because it will not automatically despawn after
27
+ * time passes, like most other effects.
28
+ */
29
+ const PICKUP_EFFECT_VARIANT = EffectVariant.LADDER;
30
+ const PICKUP_EFFECT_SUB_TYPE = 103;
31
+
32
+ /** Indexed by entity ID. */
33
+ const customPickupFunctionsMap = new Map<string, CustomPickupFunctions>();
34
+
35
+ export function customPickupInit(mod: Mod): void {
36
+ mod.AddCallback(ModCallback.PRE_PICKUP_COLLISION, prePickupCollision); // 38
37
+ mod.AddCallback(
38
+ ModCallback.POST_EFFECT_RENDER,
39
+ postEffectRenderPickupEffect,
40
+ PICKUP_EFFECT_VARIANT,
41
+ ); // 56
42
+ }
43
+
44
+ // ModCallback.PRE_PICKUP_COLLISION (38)
45
+ function prePickupCollision(
46
+ pickup: EntityPickup,
47
+ collider: Entity,
48
+ ): boolean | undefined {
49
+ const entityID = getEntityID(pickup);
50
+ const customPickupFunctions = customPickupFunctionsMap.get(entityID);
51
+ if (customPickupFunctions === undefined) {
52
+ return undefined;
53
+ }
54
+
55
+ const player = collider.ToPlayer();
56
+ if (player === undefined) {
57
+ return undefined;
58
+ }
59
+
60
+ const shouldPickup = customPickupFunctions.collisionFunc(player);
61
+ if (!shouldPickup) {
62
+ return undefined;
63
+ }
64
+
65
+ pickup.Remove();
66
+
67
+ const pickupSprite = pickup.GetSprite();
68
+ const fileName = pickupSprite.GetFilename();
69
+
70
+ const effect = spawnEffect(
71
+ PICKUP_EFFECT_VARIANT,
72
+ PICKUP_EFFECT_SUB_TYPE,
73
+ pickup.Position,
74
+ );
75
+ const effectSprite = effect.GetSprite();
76
+ effectSprite.Load(fileName, true);
77
+ effectSprite.Play("Collect", true);
78
+
79
+ customPickupFunctions.collectFunc(player);
80
+
81
+ return undefined;
82
+ }
83
+
84
+ // ModCallback.POST_EFFECT_RENDER (56)
85
+ // PICKUP_EFFECT_VARIANT
86
+ function postEffectRenderPickupEffect(effect: EntityEffect) {
87
+ if (effect.SubType !== PICKUP_EFFECT_SUB_TYPE) {
88
+ return;
89
+ }
90
+
91
+ const sprite = effect.GetSprite();
92
+ if (sprite.IsFinished("Collect")) {
93
+ effect.Remove();
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Helper function to register a custom pickup with the IsaacScript standard library. Use this
99
+ * feature for custom pickups that are intended to be picked up by the player, like keys and bombs.
100
+ *
101
+ * When IsaacScript detects that a player should be collecting the custom pickup, then the pickup
102
+ * will be immediately removed, and an effect showing the pickup's respective `Collect` animation
103
+ * will be spawned. (This emulates how a normal vanilla pickup would work.)
104
+ *
105
+ * Note that when you specify your custom pickup in the "entities2.xml" file, it should have a type
106
+ * of "5" and be associated with an anm2 file that has a "Collect" animation.
107
+ *
108
+ * @param pickupVariantCustom The variant for the corresponding custom pickup.
109
+ * @param subType The sub-type for the corresponding custom pickup.
110
+ * @param collectFunc The function to run when the player collects this pickup.
111
+ * @param collisionFunc Optional. The function to run when a player collides with the pickup.
112
+ * Default is a function that always returns true, meaning that the player will
113
+ * always immediately collect the pickup when they collide with it. Specify
114
+ * this function if your pickup should only be able to be collected under
115
+ * certain conditions.
116
+ */
117
+ export function registerCustomPickup(
118
+ pickupVariantCustom: PickupVariant,
119
+ subType: int,
120
+ collectFunc: (player: EntityPlayer) => void,
121
+ collisionFunc: (player: EntityPlayer) => boolean = () => true,
122
+ ): void {
123
+ errorIfFeaturesNotInitialized(FEATURE_NAME);
124
+
125
+ const entityID = getEntityIDFromConstituents(
126
+ EntityType.PICKUP,
127
+ pickupVariantCustom,
128
+ subType,
129
+ );
130
+ const customPickupFunctions: CustomPickupFunctions = {
131
+ collectFunc,
132
+ collisionFunc,
133
+ };
134
+ customPickupFunctionsMap.set(entityID, customPickupFunctions);
135
+ }
@@ -14,7 +14,7 @@ import { getRandomArrayElement } from "../../functions/array";
14
14
  import { spawnEffectWithSeed } from "../../functions/entitiesSpecific";
15
15
  import { newRNG } from "../../functions/rng";
16
16
  import { isLRoom, isNarrowRoom } from "../../functions/roomShape";
17
- import { trimPrefix } from "../../functions/string";
17
+ import { removeCharactersBefore, trimPrefix } from "../../functions/string";
18
18
  import { erange, irange } from "../../functions/utils";
19
19
  import { CustomStage } from "../../interfaces/private/CustomStage";
20
20
  import { ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH } from "./customStageConstants";
@@ -83,7 +83,7 @@ const N_FLOOR_ANM2_LAYERS: readonly int[] = [18, 19];
83
83
  * time passes, like most other effects.
84
84
  */
85
85
  const BACKDROP_EFFECT_VARIANT = EffectVariant.LADDER;
86
- const BACKDROP_EFFECT_SUBTYPE = 101;
86
+ const BACKDROP_EFFECT_SUB_TYPE = 101;
87
87
 
88
88
  const BACKDROP_ROOM_TYPE_SET: ReadonlySet<RoomType> = new Set([
89
89
  RoomType.DEFAULT,
@@ -118,7 +118,8 @@ function getBackdropPNGPath(
118
118
  : customStage.backdropPNGPaths;
119
119
 
120
120
  const pathArray = backdrop[backdropKind];
121
- return getRandomArrayElement(pathArray, rng);
121
+ const randomPath = getRandomArrayElement(pathArray, rng);
122
+ return removeCharactersBefore(randomPath, "gfx/");
122
123
  }
123
124
 
124
125
  function spawnWallEntity(
@@ -134,7 +135,7 @@ function spawnWallEntity(
134
135
  const seed = 1 as Seed;
135
136
  const wallEffect = spawnEffectWithSeed(
136
137
  BACKDROP_EFFECT_VARIANT,
137
- BACKDROP_EFFECT_SUBTYPE,
138
+ BACKDROP_EFFECT_SUB_TYPE,
138
139
  VectorZero,
139
140
  seed,
140
141
  );
@@ -14,6 +14,7 @@ import {
14
14
  getTrinkets,
15
15
  } from "../../functions/pickupsSpecific";
16
16
  import { calculateStageType } from "../../functions/stage";
17
+ import { removeCharactersBefore } from "../../functions/string";
17
18
  import { vectorEquals } from "../../functions/vector";
18
19
  import { CustomStage } from "../../interfaces/private/CustomStage";
19
20
  import { isCustomGridEntity } from "../customGridEntity";
@@ -31,7 +32,10 @@ export function setCustomDecorationGraphics(
31
32
  ): void {
32
33
  // If the end-user did not specify custom decoration graphics, default to Basement graphics. (We
33
34
  // don't have to adjust anything for this case.)
34
- if (customStage.decorationsPNGPath === undefined) {
35
+ if (
36
+ customStage.decorationsPNGPath === undefined &&
37
+ customStage.decorationsANM2Path === undefined
38
+ ) {
35
39
  return;
36
40
  }
37
41
 
@@ -47,8 +51,22 @@ export function setCustomDecorationGraphics(
47
51
  const sprite = gridEntity.GetSprite();
48
52
  const fileName = sprite.GetFilename();
49
53
  // On Windows, this is: gfx/grid/Props_01_Basement.anm2
50
- if (fileName.toLowerCase() === "gfx/grid/props_01_basement.anm2") {
51
- sprite.ReplaceSpritesheet(0, customStage.decorationsPNGPath);
54
+ if (fileName.toLowerCase() !== "gfx/grid/props_01_basement.anm2") {
55
+ return;
56
+ }
57
+
58
+ if (customStage.decorationsANM2Path !== undefined) {
59
+ const anm2Path = removeCharactersBefore(
60
+ customStage.decorationsANM2Path,
61
+ "gfx/",
62
+ );
63
+ sprite.Load(anm2Path, true);
64
+ } else if (customStage.decorationsPNGPath !== undefined) {
65
+ const pngPath = removeCharactersBefore(
66
+ customStage.decorationsPNGPath,
67
+ "gfx/",
68
+ );
69
+ sprite.ReplaceSpritesheet(0, pngPath);
52
70
  sprite.LoadGraphics();
53
71
  }
54
72
  }
@@ -60,7 +78,10 @@ export function setCustomRockGraphics(
60
78
  ): void {
61
79
  // If the end-user did not specify custom rock graphics, default to Basement graphics. (We don't
62
80
  // have to adjust anything for this case.)
63
- if (customStage.rocksPNGPath === undefined) {
81
+ if (
82
+ customStage.rocksPNGPath === undefined &&
83
+ customStage.rocksANM2Path === undefined
84
+ ) {
64
85
  return;
65
86
  }
66
87
 
@@ -75,12 +96,41 @@ export function setCustomRockGraphics(
75
96
 
76
97
  const sprite = gridEntity.GetSprite();
77
98
  const fileName = sprite.GetFilename();
78
- if (fileName === "gfx/grid/grid_rock.anm2") {
79
- sprite.ReplaceSpritesheet(0, customStage.rocksPNGPath);
80
- sprite.LoadGraphics();
81
- } else if (fileName === "gfx/grid/grid_pit.anm2") {
82
- sprite.ReplaceSpritesheet(1, customStage.rocksPNGPath);
83
- sprite.LoadGraphics();
99
+
100
+ switch (fileName) {
101
+ case "gfx/grid/grid_rock.anm2": {
102
+ // The normal case of a rock.
103
+ if (customStage.rocksANM2Path !== undefined) {
104
+ const anm2Path = removeCharactersBefore(
105
+ customStage.rocksANM2Path,
106
+ "gfx/",
107
+ );
108
+ sprite.Load(anm2Path, true);
109
+ } else if (customStage.rocksPNGPath !== undefined) {
110
+ const pngPath = removeCharactersBefore(
111
+ customStage.rocksPNGPath,
112
+ "gfx/",
113
+ );
114
+ sprite.ReplaceSpritesheet(0, pngPath);
115
+ sprite.LoadGraphics();
116
+ }
117
+
118
+ break;
119
+ }
120
+
121
+ case "gfx/grid/grid_pit.anm2": {
122
+ // The case of when a rock is blown on a pit to make a bridge.
123
+ if (customStage.rocksPNGPath !== undefined) {
124
+ const pngPath = removeCharactersBefore(
125
+ customStage.rocksPNGPath,
126
+ "gfx/",
127
+ );
128
+ sprite.ReplaceSpritesheet(1, pngPath);
129
+ sprite.LoadGraphics();
130
+ }
131
+
132
+ break;
133
+ }
84
134
  }
85
135
  }
86
136
 
@@ -95,6 +145,8 @@ export function setCustomPitGraphics(
95
145
  return;
96
146
  }
97
147
 
148
+ const pngPath = removeCharactersBefore(customStage.pitsPNGPath, "gfx/");
149
+
98
150
  if (isCustomGridEntity(gridEntity)) {
99
151
  return;
100
152
  }
@@ -107,7 +159,7 @@ export function setCustomPitGraphics(
107
159
  const sprite = gridEntity.GetSprite();
108
160
  const fileName = sprite.GetFilename();
109
161
  if (fileName === "gfx/grid/grid_pit.anm2") {
110
- sprite.ReplaceSpritesheet(0, customStage.pitsPNGPath);
162
+ sprite.ReplaceSpritesheet(0, pngPath);
111
163
  sprite.LoadGraphics();
112
164
  }
113
165
  }
@@ -136,7 +188,8 @@ export function setCustomDoorGraphics(
136
188
  const fileName = sprite.GetFilename();
137
189
  const doorPNGPath = getNewDoorPNGPath(customStage, fileName);
138
190
  if (doorPNGPath !== undefined) {
139
- sprite.ReplaceSpritesheet(0, doorPNGPath);
191
+ const fixedPath = removeCharactersBefore(doorPNGPath, "gfx/");
192
+ sprite.ReplaceSpritesheet(0, fixedPath);
140
193
  sprite.LoadGraphics();
141
194
  }
142
195
  }
@@ -2,6 +2,7 @@ import { EffectVariant, RoomShape } from "isaac-typescript-definitions";
2
2
  import { game } from "../../core/cachedClasses";
3
3
  import { getRandomArrayElement } from "../../functions/array";
4
4
  import { spawnEffectWithSeed } from "../../functions/entitiesSpecific";
5
+ import { removeCharactersBefore } from "../../functions/string";
5
6
  import { CustomStage } from "../../interfaces/private/CustomStage";
6
7
  import { ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH } from "./customStageConstants";
7
8
  import v from "./v";
@@ -17,7 +18,7 @@ type ShadowAnimation = "1x1" | "1x2" | "2x1" | "2x2";
17
18
  * time passes, like most other effects.
18
19
  */
19
20
  const SHADOW_EFFECT_VARIANT = EffectVariant.LADDER;
20
- const SHADOW_EFFECT_SUBTYPE = 102;
21
+ const SHADOW_EFFECT_SUB_TYPE = 102;
21
22
 
22
23
  /** The animation comes from StageAPI. */
23
24
  const ROOM_SHAPE_TO_SHADOW_ANIMATION: {
@@ -59,7 +60,7 @@ export function setShadows(customStage: CustomStage): void {
59
60
  const seed = 1 as Seed;
60
61
  const shadowEffect = spawnEffectWithSeed(
61
62
  SHADOW_EFFECT_VARIANT,
62
- SHADOW_EFFECT_SUBTYPE,
63
+ SHADOW_EFFECT_SUB_TYPE,
63
64
  centerPos,
64
65
  seed,
65
66
  );
@@ -68,7 +69,8 @@ export function setShadows(customStage: CustomStage): void {
68
69
  sprite.Load(`${ISAACSCRIPT_CUSTOM_STAGE_GFX_PATH}/stage-shadow.anm2`, false);
69
70
  const decorationSeed = room.GetDecorationSeed();
70
71
  const shadow = getRandomArrayElement(shadows, decorationSeed);
71
- sprite.ReplaceSpritesheet(0, shadow.pngPath);
72
+ const pngPath = removeCharactersBefore(shadow.pngPath, "gfx/");
73
+ sprite.ReplaceSpritesheet(0, pngPath);
72
74
  sprite.LoadGraphics();
73
75
  sprite.SetFrame(animation, 0);
74
76
  sprite.Color =
@@ -10,6 +10,7 @@ import { game, sfxManager } from "../../core/cachedClasses";
10
10
  import { arrayRemove } from "../../functions/array";
11
11
  import { getBosses } from "../../functions/bosses";
12
12
  import { getRoomSubType } from "../../functions/roomData";
13
+ import { removeCharactersBefore } from "../../functions/string";
13
14
  import { erange } from "../../functions/utils";
14
15
  import { CustomStage } from "../../interfaces/private/CustomStage";
15
16
  import { BOSS_NAME_PNG_FILE_NAMES } from "../../objects/bossNamePNGFileNames";
@@ -155,10 +156,18 @@ export function playVersusScreenAnimation(customStage: CustomStage): void {
155
156
  // Boss
156
157
  {
157
158
  const { namePNGPath, portraitPNGPath } = getBossPNGPaths(customStage);
158
- versusScreenSprite.ReplaceSpritesheet(BOSS_NAME_ANM2_LAYER, namePNGPath);
159
+ const trimmedNamePNGPath = removeCharactersBefore(namePNGPath, "gfx/");
159
160
  versusScreenSprite.ReplaceSpritesheet(
160
- BOSS_PORTRAIT_ANM2_LAYER,
161
+ BOSS_NAME_ANM2_LAYER,
162
+ trimmedNamePNGPath,
163
+ );
164
+ const trimmedPortraitPNGPath = removeCharactersBefore(
161
165
  portraitPNGPath,
166
+ "gfx/",
167
+ );
168
+ versusScreenSprite.ReplaceSpritesheet(
169
+ BOSS_PORTRAIT_ANM2_LAYER,
170
+ trimmedPortraitPNGPath,
162
171
  );
163
172
  }
164
173
 
@@ -41,7 +41,7 @@ const NON_ALIVE_NPCS_TYPE_VARIANT: ReadonlySet<string> = new Set([
41
41
  * Used to filter out certain NPCs when determining of an NPC is "alive" and/or should keep the
42
42
  * doors open.
43
43
  */
44
- const NON_ALIVE_NPCS_TYPE_VARIANT_SUBTYPE: ReadonlySet<string> = new Set([
44
+ const NON_ALIVE_NPCS_TYPE_VARIANT_SUB_TYPE: ReadonlySet<string> = new Set([
45
45
  `${EntityType.CHARGER}.${ChargerVariant.CHARGER}.${ChargerSubType.MY_SHADOW}`, // 23.0.1
46
46
  `${EntityType.MOTHER}.${MotherVariant.MOTHER_1}.${MotherSubType.PHASE_2}`, // 912
47
47
  ]);
@@ -81,7 +81,7 @@ export function isAliveExceptionNPC(npc: EntityNPC): boolean {
81
81
  }
82
82
 
83
83
  const entityTypeVariantSubType = `${npc.Type}.${npc.Variant}.${npc.SubType}`;
84
- if (NON_ALIVE_NPCS_TYPE_VARIANT_SUBTYPE.has(entityTypeVariantSubType)) {
84
+ if (NON_ALIVE_NPCS_TYPE_VARIANT_SUB_TYPE.has(entityTypeVariantSubType)) {
85
85
  return true;
86
86
  }
87
87
 
@@ -1,6 +1,6 @@
1
1
  import { CoinSubType } from "isaac-typescript-definitions";
2
2
  import {
3
- COIN_SUBTYPE_TO_VALUE,
3
+ COIN_SUB_TYPE_TO_VALUE,
4
4
  DEFAULT_COIN_VALUE,
5
5
  } from "../objects/coinSubTypeToValue";
6
6
  import { CHEST_PICKUP_VARIANTS } from "../sets/chestPickupVariantsSet";
@@ -14,7 +14,7 @@ import { isHeart } from "./pickupVariants";
14
14
  * sub-types.
15
15
  */
16
16
  export function getCoinValue(coinSubType: CoinSubType): int {
17
- const value = COIN_SUBTYPE_TO_VALUE[coinSubType];
17
+ const value = COIN_SUB_TYPE_TO_VALUE[coinSubType];
18
18
  // Handle modded coin sub-types.
19
19
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
20
20
  return value === undefined ? DEFAULT_COIN_VALUE : value;
@@ -10,6 +10,18 @@ export function removeAllCharacters(string: string, character: string): string {
10
10
  return string.replaceAll(character, "");
11
11
  }
12
12
 
13
+ /**
14
+ * Helper function to remove all of the characters in a string before a given substring. Returns the
15
+ * modified string.
16
+ */
17
+ export function removeCharactersBefore(
18
+ string: string,
19
+ substring: string,
20
+ ): string {
21
+ const index = string.indexOf(substring);
22
+ return string.slice(index);
23
+ }
24
+
13
25
  /**
14
26
  * Helper function to remove one or more substrings from a string, if they exist. Returns the
15
27
  * modified string.
package/src/index.ts CHANGED
@@ -25,6 +25,7 @@ export {
25
25
  removeCustomGridEntity,
26
26
  spawnCustomGridEntity,
27
27
  } from "./features/customGridEntity";
28
+ export { registerCustomPickup } from "./features/customPickup";
28
29
  export * from "./features/customStage/exports";
29
30
  export * from "./features/customTrapdoor/exports";
30
31
  export * from "./features/debugDisplay/exports";
@@ -3,6 +3,7 @@ import { characterHealthConversionInit } from "./features/characterHealthConvers
3
3
  import { characterStatsInit } from "./features/characterStats";
4
4
  import { collectibleItemPoolTypeInit } from "./features/collectibleItemPoolType";
5
5
  import { customGridEntityInit } from "./features/customGridEntity";
6
+ import { customPickupInit } from "./features/customPickup";
6
7
  import { customStageInit } from "./features/customStage/init";
7
8
  import { customTrapdoorInit } from "./features/customTrapdoor/init";
8
9
  import { deployJSONRoomInit } from "./features/deployJSONRoom";
@@ -42,6 +43,7 @@ function initFeaturesMajor(mod: ModUpgraded) {
42
43
  }
43
44
 
44
45
  function initFeaturesMinor(mod: ModUpgraded) {
46
+ customPickupInit(mod);
45
47
  customTrapdoorInit(mod);
46
48
  disableAllSoundInit(mod);
47
49
  disableInputsInit(mod);
@@ -132,9 +132,24 @@ export interface CustomStageTSConfig {
132
132
  *
133
133
  * If not specified, the vanilla Basement decorations spritesheet will be used. For reference,
134
134
  * this is located at: `resources/gfx/grid/props_01_basement.png`
135
+ *
136
+ * If you want to have custom animations for your decorations, then do not use this field and use
137
+ * `decorationsANM2Path` instead.
135
138
  */
136
139
  decorationsPNGPath?: string;
137
140
 
141
+ /**
142
+ * Optional. The full path to the anm2 file that contains the custom animations for the
143
+ * decorations of the floor.
144
+ *
145
+ * If not specified, the vanilla Basement decorations spritesheet will be used. For reference,
146
+ * this is located at: `resources/gfx/grid/props_01_basement.png`
147
+ *
148
+ * If you do not want to have custom animations for your decorations, then do not use this field
149
+ * and use `decorationsPNGPath` instead.
150
+ */
151
+ decorationsANM2Path?: string;
152
+
138
153
  /**
139
154
  * Optional. The full path to the spritesheet that contains the graphics of the rocks/blocks/urns
140
155
  * for the floor.
@@ -146,18 +161,53 @@ export interface CustomStageTSConfig {
146
161
  *
147
162
  * If not specified, the vanilla Basement rocks spritesheet will be used. For reference, this is
148
163
  * located at: `resources-dlc3/gfx/grid/rocks_basement.png`
164
+ *
165
+ * If you want to have custom animations for your rocks, then do not use this field and use
166
+ * `rocksANM2Path` instead.
149
167
  */
150
168
  rocksPNGPath?: string;
151
169
 
170
+ /**
171
+ * Optional. The full path to the anm2 file that contains the custom animations for the
172
+ * rocks/blocks/urns of the floor.
173
+ *
174
+ * If specified, it is assumed that you have your own custom rock alt type, and all vanilla
175
+ * rewards/enemies that spawn from urns will be automatically removed. Use the
176
+ * `POST_GRID_ENTITY_BROKEN` callback to make your own custom rewards. Or, if you want to emulate
177
+ * a vanilla urn/mushroom/skull/polyp/bucket, use the `spawnRockAltReward` helper function.
178
+ *
179
+ * If not specified, the vanilla Basement rocks spritesheet will be used. For reference, this is
180
+ * located at: `resources-dlc3/gfx/grid/rocks_basement.png`
181
+ *
182
+ * If you do not want to have custom animations for your rocks, then do not use this field and use
183
+ * `rocksPNGPath` instead.
184
+ */
185
+ rocksANM2Path?: string;
186
+
152
187
  /**
153
188
  * Optional. The full path to the spritesheet that contains the graphics of the pits for the
154
189
  * floor.
155
190
  *
156
191
  * If not specified, the vanilla Basement pits spritesheet will be used. For reference, this is
157
192
  * located at: `resources/gfx/grid/grid_pit.png`
193
+ *
194
+ * If you do not want to have custom animations for your pits, then do not use this field and use
195
+ * `pitsANM2Path` instead.
158
196
  */
159
197
  pitsPNGPath?: string;
160
198
 
199
+ /**
200
+ * Optional. The full path to the anm2 file that contains the custom animations for the pits of
201
+ * the floor.
202
+ *
203
+ * If not specified, the vanilla Basement pits spritesheet will be used. For reference, this is
204
+ * located at: `resources/gfx/grid/grid_pit.png`
205
+ *
206
+ * If you do not want to have custom animations for your pits, then do not use this field and use
207
+ * `pitsPNGPath` instead.
208
+ */
209
+ pitsANM2Path?: string;
210
+
161
211
  /**
162
212
  * Optional. A collection of paths that contain graphics for the doors of the floor. If not
163
213
  * specified, the doors for Basement will be used.
@@ -2,7 +2,7 @@ import { CoinSubType } from "isaac-typescript-definitions";
2
2
 
3
3
  export const DEFAULT_COIN_VALUE = 1;
4
4
 
5
- export const COIN_SUBTYPE_TO_VALUE: { readonly [key in CoinSubType]: int } = {
5
+ export const COIN_SUB_TYPE_TO_VALUE: { readonly [key in CoinSubType]: int } = {
6
6
  [CoinSubType.NULL]: 0, // 0
7
7
  [CoinSubType.PENNY]: 1, // 1
8
8
  [CoinSubType.NICKEL]: 5, // 2