isaacscript-common 15.1.0 → 15.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isaacscript-common",
3
- "version": "15.1.0",
3
+ "version": "15.1.1",
4
4
  "description": "Helper functions and features for IsaacScript mods.",
5
5
  "keywords": [
6
6
  "isaac",
@@ -1,3 +1,5 @@
1
+ import { ModCallback } from "isaac-typescript-definitions";
2
+ import { ModCallbackCustom } from "../enums/ModCallbackCustom";
1
3
  import {
2
4
  getTSTLClassConstructor,
3
5
  getTSTLClassName,
@@ -20,10 +22,20 @@ type ModFeatureConstructor = TSTLClassMetatable["constructor"] & {
20
22
  * mod features from this class in order to enable the `@Callback` and `@CustomCallback` decorators
21
23
  * that automatically subscribe to callbacks.
22
24
  *
25
+ * If your feature has variables that are managed by the save data manager, put them as a `v` class
26
+ * member and they will automatically be registered with the save data manager when the class is
27
+ * instantiated.
28
+ *
23
29
  * For example:
24
30
  *
25
31
  * ```ts
26
32
  * export class MyFeature extends ModFeature {
33
+ * v = {
34
+ * run: {
35
+ * foo: 123,
36
+ * }
37
+ * }
38
+ *
27
39
  * @Callback(ModCallback.POST_GAME_STARTED)
28
40
  * postGameStarted(isContinued: boolean): void {
29
41
  * Isaac.DebugString(`Callback fired: POST_GAME_STARTED`);
@@ -40,8 +52,8 @@ export class ModFeature {
40
52
  }
41
53
 
42
54
  const modFeatureConstructor = constructor as ModFeatureConstructor;
43
- checkAddDecoratedCallbacks(mod, modFeatureConstructor);
44
- checkAddDecoratedCallbacksCustom(mod, modFeatureConstructor);
55
+ checkAddDecoratedCallbacks(mod, modFeatureConstructor, this);
56
+ checkAddDecoratedCallbacksCustom(mod, modFeatureConstructor, this);
45
57
  checkRegisterSaveDataManager(mod, this);
46
58
  }
47
59
  }
@@ -49,22 +61,50 @@ export class ModFeature {
49
61
  function checkAddDecoratedCallbacks(
50
62
  mod: ModUpgradedBase,
51
63
  modFeatureConstructor: ModFeatureConstructor,
64
+ modFeature: ModFeature,
52
65
  ) {
53
66
  const addCallbackArgs = modFeatureConstructor[ADD_CALLBACK_ARGS_KEY];
54
67
  if (addCallbackArgs === undefined) {
55
68
  return;
56
69
  }
57
70
 
71
+ const tstlClassName = getTSTLClassName(modFeatureConstructor) ?? "Unknown";
72
+
58
73
  for (const args of addCallbackArgs) {
74
+ const parameters = args as unknown[];
75
+ const modCallback = parameters.shift() as ModCallback | undefined;
76
+ if (modCallback === undefined) {
77
+ error(
78
+ `Failed to get the ModCallback from the parameters for class: ${tstlClassName}`,
79
+ );
80
+ }
81
+
82
+ const callback = parameters.shift() as
83
+ | ((this: void, ...callbackArgs: unknown[]) => void)
84
+ | undefined;
85
+ if (callback === undefined) {
86
+ error(
87
+ `Failed to get the callback from the parameters for class: ${tstlClassName}`,
88
+ );
89
+ }
90
+
91
+ /**
92
+ * We need to wrap the callback in a new function so that we can explicitly pass the class as
93
+ * the first argument. (Otherwise, the method will not be able to properly access `this`.
94
+ */
95
+ const newCallback = (...callbackArgs: unknown[]) => {
96
+ callback(modFeature, ...callbackArgs);
97
+ };
98
+
59
99
  // @ts-expect-error The compiler does not know that the arguments match the method.
60
- // eslint-disable-next-line isaacscript/strict-enums
61
- mod.AddCallback(...args);
100
+ mod.AddCallback(modCallback, newCallback, ...parameters);
62
101
  }
63
102
  }
64
103
 
65
104
  function checkAddDecoratedCallbacksCustom(
66
105
  mod: ModUpgradedBase,
67
106
  modFeatureConstructor: ModFeatureConstructor,
107
+ modFeature: ModFeature,
68
108
  ) {
69
109
  const addCallbackCustomArgs =
70
110
  modFeatureConstructor[ADD_CALLBACK_CUSTOM_ARGS_KEY];
@@ -72,10 +112,38 @@ function checkAddDecoratedCallbacksCustom(
72
112
  return;
73
113
  }
74
114
 
115
+ const tstlClassName = getTSTLClassName(modFeatureConstructor) ?? "Unknown";
116
+
75
117
  for (const args of addCallbackCustomArgs) {
118
+ const parameters = args as unknown[];
119
+ const modCallbackCustom = parameters.shift() as
120
+ | ModCallbackCustom
121
+ | undefined;
122
+ if (modCallbackCustom === undefined) {
123
+ error(
124
+ `Failed to get the ModCallbackCustom from the parameters for class: ${tstlClassName}`,
125
+ );
126
+ }
127
+
128
+ const callback = parameters.shift() as
129
+ | ((this: void, ...callbackArgs: unknown[]) => void)
130
+ | undefined;
131
+ if (callback === undefined) {
132
+ error(
133
+ `Failed to get the callback from the parameters for class: ${tstlClassName}`,
134
+ );
135
+ }
136
+
137
+ /**
138
+ * We need to wrap the callback in a new function so that we can explicitly pass the class as
139
+ * the first argument. (Otherwise, the method will not be able to properly access `this`.
140
+ */
141
+ const newCallback = (...callbackArgs: unknown[]) => {
142
+ callback(modFeature, ...callbackArgs);
143
+ };
144
+
76
145
  // @ts-expect-error The compiler does not know that the arguments match the method.
77
- // eslint-disable-next-line isaacscript/strict-enums
78
- mod.AddCallbackCustom(...args);
146
+ mod.AddCallbackCustom(modCallbackCustom, newCallback, ...parameters);
79
147
  }
80
148
  }
81
149
 
@@ -61,7 +61,7 @@ export function forEach<T>(
61
61
  /**
62
62
  * Helper function for non-TypeScript users to check if an element is in an array.
63
63
  *
64
- * Since this takes O(N) time, using this function is usually a mistake, since you can use a `Map`
64
+ * Since this takes O(N) time, using this function is usually a mistake, since you can use a `Set`
65
65
  * data structure to get O(1) lookups.
66
66
  *
67
67
  * Internally, this just calls `array.includes`.
@@ -29,6 +29,7 @@ import {
29
29
  import { ModCallbackCustom } from "../enums/ModCallbackCustom";
30
30
  import { AddCallbackParametersCustom } from "../interfaces/private/AddCallbackParametersCustom";
31
31
  import { AllButFirst } from "../types/AllButFirst";
32
+ import { getTSTLClassName } from "./tstlClass";
32
33
 
33
34
  /**
34
35
  * A decorator function that signifies that the decorated class method should be automatically
@@ -57,10 +58,17 @@ export function Callback<T extends ModCallback>(
57
58
  // Since the decorator runs prior to instantiation, we only have access to get and set static
58
59
  // properties, which are located on the "constructor" table. Thus, we store the callback
59
60
  // arguments for later.
60
- const constructor = target.constructor as unknown as LuaTable<
61
- AnyNotNil,
62
- unknown
63
- >;
61
+ const constructor = target.constructor as unknown as
62
+ | LuaTable<AnyNotNil, unknown>
63
+ | undefined;
64
+
65
+ if (constructor === undefined) {
66
+ const tstlClassName = getTSTLClassName(target) ?? "Unknown";
67
+ error(
68
+ `Failed to get the constructor for class "${tstlClassName}". Did you decorate a static method? You can only decorate non-static class methods, because the "Mod" object is not present before the class is instantiated.`,
69
+ );
70
+ }
71
+
64
72
  if (!constructor.has(ADD_CALLBACK_ARGS_KEY)) {
65
73
  constructor.set(ADD_CALLBACK_ARGS_KEY, []);
66
74
  }
@@ -100,10 +108,17 @@ export function CallbackCustom<T extends ModCallbackCustom>(
100
108
  // Since the decorator runs prior to instantiation, we only have access to get and set static
101
109
  // properties, which are located on the "constructor" table. Thus, we store the callback
102
110
  // arguments for later.
103
- const constructor = target.constructor as unknown as LuaTable<
104
- AnyNotNil,
105
- unknown
106
- >;
111
+ const constructor = target.constructor as unknown as
112
+ | LuaTable<AnyNotNil, unknown>
113
+ | undefined;
114
+
115
+ if (constructor === undefined) {
116
+ const tstlClassName = getTSTLClassName(target) ?? "Unknown";
117
+ error(
118
+ `Failed to get the constructor for class "${tstlClassName}". Did you decorate a static method? You can only decorate non-static class methods, because the "Mod" object is not present before the class is instantiated.`,
119
+ );
120
+ }
121
+
107
122
  if (!constructor.has(ADD_CALLBACK_CUSTOM_ARGS_KEY)) {
108
123
  constructor.set(ADD_CALLBACK_CUSTOM_ARGS_KEY, []);
109
124
  }
@@ -3,6 +3,16 @@
3
3
  * adds it to the player.
4
4
  * - This function should only be used inside the `EVALUATE_CACHE` callback.
5
5
  * - In this context, the "tears stat" represents what is shown on the in-game stat UI.
6
+ *
7
+ * For example:
8
+ *
9
+ * ```ts
10
+ * function evaluateCacheTears(player: EntityPlayer) {
11
+ * const numFoo = player.GetNumCollectible(CollectibleTypeCustom.FOO);
12
+ * const tearsStat = numFoo * FOO_TEARS_STAT;
13
+ * addTearsStat(player, tearsStat);
14
+ * }
15
+ * ```
6
16
  */
7
17
  export function addTearsStat(player: EntityPlayer, tearsStat: float): void {
8
18
  const existingTearsStat = getTearsStat(player.MaxFireDelay);