isaacscript-common 6.15.0 → 6.16.2
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/dist/callbacks/postGridEntityCustomCollision.d.ts +2 -0
- package/dist/callbacks/postGridEntityCustomCollision.d.ts.map +1 -0
- package/dist/callbacks/postGridEntityCustomCollision.lua +71 -0
- package/dist/callbacks/postGridEntityCustomRender.lua +0 -7
- package/dist/callbacks/postGridEntityCustomUpdate.lua +0 -7
- package/dist/callbacks/subscriptions/postGridEntityCustomBroken.d.ts +6 -0
- package/dist/callbacks/subscriptions/postGridEntityCustomBroken.d.ts.map +1 -0
- package/dist/callbacks/subscriptions/postGridEntityCustomBroken.lua +24 -0
- package/dist/callbacks/subscriptions/postGridEntityCustomCollision.d.ts +6 -0
- package/dist/callbacks/subscriptions/postGridEntityCustomCollision.d.ts.map +1 -0
- package/dist/callbacks/subscriptions/postGridEntityCustomCollision.lua +29 -0
- package/dist/enums/ModCallbackCustom.d.ts +96 -62
- package/dist/enums/ModCallbackCustom.d.ts.map +1 -1
- package/dist/enums/ModCallbackCustom.lua +64 -60
- package/dist/features/customGridEntity.d.ts +5 -1
- package/dist/features/customGridEntity.d.ts.map +1 -1
- package/dist/features/customGridEntity.lua +65 -5
- package/dist/functions/color.d.ts +11 -15
- package/dist/functions/color.d.ts.map +1 -1
- package/dist/functions/color.lua +55 -74
- package/dist/functions/deepCopy.d.ts.map +1 -1
- package/dist/functions/deepCopy.lua +22 -1
- package/dist/functions/gridEntities.d.ts +0 -16
- package/dist/functions/gridEntities.d.ts.map +1 -1
- package/dist/functions/gridEntities.lua +0 -19
- package/dist/functions/isaacAPIClass.d.ts +1 -1
- package/dist/functions/isaacAPIClass.d.ts.map +1 -1
- package/dist/functions/kColor.d.ts +11 -15
- package/dist/functions/kColor.d.ts.map +1 -1
- package/dist/functions/kColor.lua +42 -61
- package/dist/functions/mergeTests.lua +2 -2
- package/dist/functions/npcs.d.ts +1 -29
- package/dist/functions/npcs.d.ts.map +1 -1
- package/dist/functions/npcs.lua +0 -45
- package/dist/functions/projectiles.d.ts +32 -0
- package/dist/functions/projectiles.d.ts.map +1 -0
- package/dist/functions/projectiles.lua +73 -0
- package/dist/functions/rng.d.ts +11 -15
- package/dist/functions/rng.d.ts.map +1 -1
- package/dist/functions/rng.lua +32 -52
- package/dist/functions/rockAlt.d.ts +27 -3
- package/dist/functions/rockAlt.d.ts.map +1 -1
- package/dist/functions/rockAlt.lua +113 -33
- package/dist/functions/saveFile.d.ts.map +1 -1
- package/dist/functions/saveFile.lua +3 -1
- package/dist/functions/serialization.d.ts +26 -7
- package/dist/functions/serialization.d.ts.map +1 -1
- package/dist/functions/serialization.lua +51 -19
- package/dist/functions/table.d.ts +2 -2
- package/dist/functions/table.lua +2 -2
- package/dist/functions/tstlClass.d.ts +1 -1
- package/dist/functions/tstlClass.d.ts.map +1 -1
- package/dist/functions/vector.d.ts +11 -15
- package/dist/functions/vector.d.ts.map +1 -1
- package/dist/functions/vector.lua +36 -55
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.lua +8 -0
- package/dist/initCustomCallbacks.d.ts.map +1 -1
- package/dist/initCustomCallbacks.lua +3 -0
- package/dist/interfaces/AddCallbackParameterCustom.d.ts +4 -0
- package/dist/interfaces/AddCallbackParameterCustom.d.ts.map +1 -1
- package/dist/objects/callbackRegisterFunctions.d.ts.map +1 -1
- package/dist/objects/callbackRegisterFunctions.lua +6 -0
- package/dist/objects/isaacAPIClassTypeToFunctions.d.ts +12 -0
- package/dist/objects/isaacAPIClassTypeToFunctions.d.ts.map +1 -0
- package/dist/objects/isaacAPIClassTypeToFunctions.lua +25 -0
- package/dist/types/{private/IsaacAPIClass.d.ts → IsaacAPIClass.d.ts} +0 -0
- package/dist/types/IsaacAPIClass.d.ts.map +1 -0
- package/dist/types/{private/IsaacAPIClass.lua → IsaacAPIClass.lua} +0 -0
- package/dist/types/{private/SerializedIsaacAPIClass.d.ts → SerializedIsaacAPIClass.d.ts} +0 -0
- package/dist/types/SerializedIsaacAPIClass.d.ts.map +1 -0
- package/dist/types/{private/SerializedIsaacAPIClass.lua → SerializedIsaacAPIClass.lua} +0 -0
- package/dist/types/{private/TSTLClass.d.ts → TSTLClass.d.ts} +0 -0
- package/dist/types/TSTLClass.d.ts.map +1 -0
- package/dist/types/{private/TSTLClass.lua → TSTLClass.lua} +0 -0
- package/package.json +2 -2
- package/src/callbacks/postGridEntityCustomCollision.ts +74 -0
- package/src/callbacks/postGridEntityCustomRender.ts +1 -17
- package/src/callbacks/postGridEntityCustomUpdate.ts +1 -17
- package/src/callbacks/subscriptions/postGridEntityCustomBroken.ts +36 -0
- package/src/callbacks/subscriptions/postGridEntityCustomCollision.ts +43 -0
- package/src/enums/ModCallbackCustom.ts +38 -2
- package/src/features/customGridEntity.ts +76 -0
- package/src/functions/color.ts +67 -87
- package/src/functions/deepCopy.ts +15 -2
- package/src/functions/gridEntities.ts +0 -23
- package/src/functions/isaacAPIClass.ts +1 -1
- package/src/functions/kColor.ts +65 -84
- package/src/functions/mergeTests.ts +2 -2
- package/src/functions/npcs.ts +1 -58
- package/src/functions/projectiles.ts +78 -0
- package/src/functions/rng.ts +45 -65
- package/src/functions/rockAlt.ts +127 -40
- package/src/functions/saveFile.ts +2 -1
- package/src/functions/serialization.ts +81 -25
- package/src/functions/table.ts +2 -2
- package/src/functions/tstlClass.ts +1 -1
- package/src/functions/vector.ts +55 -74
- package/src/index.ts +1 -0
- package/src/initCustomCallbacks.ts +2 -0
- package/src/interfaces/AddCallbackParameterCustom.ts +4 -0
- package/src/objects/callbackRegisterFunctions.ts +6 -0
- package/src/objects/isaacAPIClassTypeToFunctions.ts +63 -0
- package/src/types/{private/IsaacAPIClass.ts → IsaacAPIClass.ts} +0 -0
- package/src/types/{private/SerializedIsaacAPIClass.ts → SerializedIsaacAPIClass.ts} +0 -0
- package/src/types/{private/TSTLClass.ts → TSTLClass.ts} +0 -0
- package/dist/objects/isaacAPIClassTypeToCopyFunction.d.ts +0 -6
- package/dist/objects/isaacAPIClassTypeToCopyFunction.d.ts.map +0 -1
- package/dist/objects/isaacAPIClassTypeToCopyFunction.lua +0 -13
- package/dist/objects/serializedIsaacAPIClassTypeToIdentityFunction.d.ts +0 -5
- package/dist/objects/serializedIsaacAPIClassTypeToIdentityFunction.d.ts.map +0 -1
- package/dist/objects/serializedIsaacAPIClassTypeToIdentityFunction.lua +0 -13
- package/dist/types/private/IsaacAPIClass.d.ts.map +0 -1
- package/dist/types/private/SerializedIsaacAPIClass.d.ts.map +0 -1
- package/dist/types/private/TSTLClass.d.ts.map +0 -1
- package/src/objects/isaacAPIClassTypeToCopyFunction.ts +0 -18
- package/src/objects/serializedIsaacAPIClassTypeToIdentityFunction.ts +0 -14
|
@@ -5,7 +5,7 @@ import { deepCopy } from "./deepCopy";
|
|
|
5
5
|
import { log } from "./log";
|
|
6
6
|
import { isRNG, newRNG } from "./rng";
|
|
7
7
|
import { isSerializedIsaacAPIClass } from "./serialization";
|
|
8
|
-
import {
|
|
8
|
+
import { isVector, serializeVector } from "./vector";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Run the suite of tests that prove that the "merge" function works properly. (This function is not
|
|
@@ -92,7 +92,7 @@ function oldTableHasSerializedIsaacAPIClass() {
|
|
|
92
92
|
const y = 60;
|
|
93
93
|
const vector = Vector(x, y);
|
|
94
94
|
|
|
95
|
-
const vectorSerialized =
|
|
95
|
+
const vectorSerialized = serializeVector(vector);
|
|
96
96
|
if (!isSerializedIsaacAPIClass(vectorSerialized)) {
|
|
97
97
|
error(
|
|
98
98
|
'The "isSerializedIsaacAPIClass" function says that a serialized vector is not serialized.',
|
package/src/functions/npcs.ts
CHANGED
|
@@ -11,13 +11,11 @@ import {
|
|
|
11
11
|
MotherVariant,
|
|
12
12
|
NpcState,
|
|
13
13
|
PeepVariant,
|
|
14
|
-
ProjectilesMode,
|
|
15
14
|
RaglingVariant,
|
|
16
15
|
VisVariant,
|
|
17
16
|
} from "isaac-typescript-definitions";
|
|
18
17
|
import { EGGY_STATE_FRAME_OF_FINAL_SPIDER } from "../constants";
|
|
19
|
-
import {
|
|
20
|
-
import { getNPCs, getProjectiles } from "./entitiesSpecific";
|
|
18
|
+
import { getNPCs } from "./entitiesSpecific";
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
21
|
* Used to filter out certain NPCs when determining of an NPC is "alive" and/or should keep the
|
|
@@ -46,33 +44,6 @@ const NON_ALIVE_NPCS_TYPE_VARIANT_SUBTYPE: ReadonlySet<string> = new Set([
|
|
|
46
44
|
`${EntityType.MOTHER}.${MotherVariant.MOTHER_1}.${MotherSubType.PHASE_2}`, // 912
|
|
47
45
|
]);
|
|
48
46
|
|
|
49
|
-
/**
|
|
50
|
-
* Helper function to make an NPC fire a projectile. Returns the fired projectile. Use this function
|
|
51
|
-
* instead of the `EntityNPC.FireProjectiles` method, since that returns void.
|
|
52
|
-
*
|
|
53
|
-
* @param npc The NPC to fire the projectile from.
|
|
54
|
-
* @param position The staring position of the projectile.
|
|
55
|
-
* @param velocity The starting velocity of the projectile.
|
|
56
|
-
* @param projectilesMode The mode of the projectile. Optional. Default is
|
|
57
|
-
* `ProjectilesMode.ONE_PROJECTILE`.
|
|
58
|
-
* @param projectileParams The parameters of the projectile. Optional. Default is
|
|
59
|
-
* `ProjectileParams()`.
|
|
60
|
-
* @returns The fired projectile.
|
|
61
|
-
*/
|
|
62
|
-
export function fireProjectiles(
|
|
63
|
-
npc: EntityNPC,
|
|
64
|
-
position: Vector,
|
|
65
|
-
velocity: Vector,
|
|
66
|
-
projectilesMode: ProjectilesMode = ProjectilesMode.ONE_PROJECTILE,
|
|
67
|
-
projectileParams: ProjectileParams = ProjectileParams(),
|
|
68
|
-
): EntityProjectile[] {
|
|
69
|
-
const oldProjectiles = getProjectiles(projectileParams.Variant);
|
|
70
|
-
npc.FireProjectiles(position, velocity, projectilesMode, projectileParams);
|
|
71
|
-
const newProjectiles = getProjectiles(projectileParams.Variant);
|
|
72
|
-
|
|
73
|
-
return getFilteredNewEntities(oldProjectiles, newProjectiles);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
47
|
/**
|
|
77
48
|
* Helper function to get all of the non-dead NPCs in the room.
|
|
78
49
|
*
|
|
@@ -149,31 +120,3 @@ export function isRaglingDeathPatch(npc: EntityNPC): boolean {
|
|
|
149
120
|
npc.State === NpcState.SPECIAL
|
|
150
121
|
);
|
|
151
122
|
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* The base game `EntityNPC.FireProjectiles` method does not return anything, which is a problem in
|
|
155
|
-
* situations where you need to work with the fired projectiles. This function runs that method, and
|
|
156
|
-
* then returns the projectiles that were spawned.
|
|
157
|
-
*
|
|
158
|
-
* @param npc The EntityNPC firing projectiles.
|
|
159
|
-
* @param position The starting position of the projectiles.
|
|
160
|
-
* @param velocity The starting velocity of the projectiles.
|
|
161
|
-
* @param projectilesMode A ProjectilesMode enum value defining how to fire the projectiles.
|
|
162
|
-
* @param projectileParams A ProjectileParams object containing various parameters for the
|
|
163
|
-
* projectiles.
|
|
164
|
-
* @returns An array of EntityProjectiles containing all fired projectiles.
|
|
165
|
-
*/
|
|
166
|
-
export function npcFireProjectiles(
|
|
167
|
-
npc: EntityNPC,
|
|
168
|
-
position: Vector,
|
|
169
|
-
velocity: Vector,
|
|
170
|
-
projectilesMode: ProjectilesMode,
|
|
171
|
-
projectileParams: ProjectileParams,
|
|
172
|
-
): EntityProjectile[] {
|
|
173
|
-
const oldEntities = getProjectiles();
|
|
174
|
-
npc.FireProjectiles(position, velocity, projectilesMode, projectileParams);
|
|
175
|
-
const newEntities = getProjectiles();
|
|
176
|
-
const filteredNewEntities = getFilteredNewEntities(oldEntities, newEntities);
|
|
177
|
-
|
|
178
|
-
return filteredNewEntities;
|
|
179
|
-
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EntityFlag,
|
|
3
|
+
EntityType,
|
|
4
|
+
ProjectilesMode,
|
|
5
|
+
} from "isaac-typescript-definitions";
|
|
6
|
+
import { getFilteredNewEntities } from "./entities";
|
|
7
|
+
import { getProjectiles, spawnNPC } from "./entitiesSpecific";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Helper function to make an NPC fire one or more projectiles. Returns the fired projectile(s).
|
|
11
|
+
*
|
|
12
|
+
* Use this function instead of the `EntityNPC.FireProjectiles` method if you need to modify or
|
|
13
|
+
* access the `EntityProjectile` objects after they are fired, since this function returns the
|
|
14
|
+
* objects in an array.
|
|
15
|
+
*
|
|
16
|
+
* @param npc The NPC to fire the projectile(s) from. You can also pass undefined if you do not want
|
|
17
|
+
* the projectile(s) to come from anything in particular.
|
|
18
|
+
* @param position The staring position of the projectile(s).
|
|
19
|
+
* @param velocity The starting velocity of the projectile(s).
|
|
20
|
+
* @param projectilesMode Optional. The mode of the projectile(s). Default is
|
|
21
|
+
* `ProjectilesMode.ONE_PROJECTILE`.
|
|
22
|
+
* @param projectileParams Optional. The parameters of the projectile(s). Default is
|
|
23
|
+
* `ProjectileParams()`.
|
|
24
|
+
* @returns The fired projectile(s).
|
|
25
|
+
*/
|
|
26
|
+
export function fireProjectiles(
|
|
27
|
+
npc: EntityNPC | undefined,
|
|
28
|
+
position: Vector,
|
|
29
|
+
velocity: Vector,
|
|
30
|
+
projectilesMode: ProjectilesMode = ProjectilesMode.ONE_PROJECTILE,
|
|
31
|
+
projectileParams: ProjectileParams = ProjectileParams(),
|
|
32
|
+
): EntityProjectile[] {
|
|
33
|
+
const oldProjectiles = getProjectiles(projectileParams.Variant);
|
|
34
|
+
|
|
35
|
+
let spawnedFly = false;
|
|
36
|
+
if (npc === undefined) {
|
|
37
|
+
// Since the `EntityNPC.FireProjectiles` method is not static, we arbitrarily spawn a fly.
|
|
38
|
+
spawnedFly = true;
|
|
39
|
+
npc = spawnNPC(EntityType.FLY, 0, 0, position);
|
|
40
|
+
npc.Visible = false;
|
|
41
|
+
npc.ClearEntityFlags(EntityFlag.APPEAR);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
npc.FireProjectiles(position, velocity, projectilesMode, projectileParams);
|
|
45
|
+
const newProjectiles = getProjectiles(projectileParams.Variant);
|
|
46
|
+
|
|
47
|
+
if (spawnedFly) {
|
|
48
|
+
npc.Remove();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return getFilteredNewEntities(oldProjectiles, newProjectiles);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Helper function to spawn projectiles in a circle around a position. Under the hood, this
|
|
56
|
+
* leverages `ProjectileMode.N_PROJECTILES_IN_CIRCLE`.
|
|
57
|
+
*
|
|
58
|
+
* @param npc The NPC to fire the projectile(s) from. You can also pass undefined if you do not want
|
|
59
|
+
* the projectile(s) to come from anything in particular.
|
|
60
|
+
* @param position The staring position of the projectile(s).
|
|
61
|
+
* @param speed The speed of the projectile(s).
|
|
62
|
+
* @param numProjectiles The amount of projectiles to spawn.
|
|
63
|
+
* @returns The fired projectile(s).
|
|
64
|
+
*/
|
|
65
|
+
export function fireProjectilesInCircle(
|
|
66
|
+
npc: EntityNPC | undefined,
|
|
67
|
+
position: Vector,
|
|
68
|
+
speed: float,
|
|
69
|
+
numProjectiles: int,
|
|
70
|
+
): EntityProjectile[] {
|
|
71
|
+
const velocity = Vector(speed, numProjectiles);
|
|
72
|
+
return fireProjectiles(
|
|
73
|
+
npc,
|
|
74
|
+
position,
|
|
75
|
+
velocity,
|
|
76
|
+
ProjectilesMode.N_PROJECTILES_IN_CIRCLE,
|
|
77
|
+
);
|
|
78
|
+
}
|
package/src/functions/rng.ts
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { game } from "../cachedClasses";
|
|
2
2
|
import { SerializationBrand } from "../enums/private/SerializationBrand";
|
|
3
|
-
import { SerializationType } from "../enums/SerializationType";
|
|
4
3
|
import { isaacAPIClassEquals, isIsaacAPIClassOfType } from "./isaacAPIClass";
|
|
5
4
|
import { getNumbersFromTable, tableHasKeys } from "./table";
|
|
6
5
|
import { isTable } from "./types";
|
|
7
6
|
|
|
8
|
-
type SerializedRNG = LuaMap<string, unknown> & {
|
|
7
|
+
export type SerializedRNG = LuaMap<string, unknown> & {
|
|
9
8
|
readonly __serializedRNGBrand: symbol;
|
|
10
9
|
};
|
|
11
10
|
|
|
12
|
-
interface CopyRNGReturn {
|
|
13
|
-
[SerializationType.NONE]: RNG;
|
|
14
|
-
[SerializationType.SERIALIZE]: SerializedRNG;
|
|
15
|
-
[SerializationType.DESERIALIZE]: RNG;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
11
|
/**
|
|
19
12
|
* This is the ShiftIdx that Blade recommended after having reviewing the game's internal functions.
|
|
20
13
|
* Any value between 0 and 80 should work equally well.
|
|
@@ -25,66 +18,35 @@ const RECOMMENDED_SHIFT_IDX = 35;
|
|
|
25
18
|
const KEYS = ["seed"];
|
|
26
19
|
const OBJECT_NAME = "RNG";
|
|
27
20
|
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export function copyRNG<
|
|
36
|
-
R extends RNG | SerializedRNG,
|
|
37
|
-
S extends SerializationType,
|
|
38
|
-
>(rng: R, serializationType: S): CopyRNGReturn[S];
|
|
39
|
-
export function copyRNG<R extends RNG | SerializedRNG>(
|
|
40
|
-
rng: R,
|
|
41
|
-
): CopyRNGReturn[SerializationType.NONE];
|
|
42
|
-
export function copyRNG(
|
|
43
|
-
rng: RNG | SerializedRNG,
|
|
44
|
-
serializationType = SerializationType.NONE,
|
|
45
|
-
): CopyRNGReturn[keyof CopyRNGReturn] {
|
|
46
|
-
switch (serializationType) {
|
|
47
|
-
case SerializationType.NONE: {
|
|
48
|
-
if (!isRNG(rng)) {
|
|
49
|
-
error(
|
|
50
|
-
`Failed to copy a ${OBJECT_NAME} object since the provided object was not a userdata ${OBJECT_NAME} class.`,
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const seed = rng.GetSeed();
|
|
55
|
-
return newRNG(seed);
|
|
56
|
-
}
|
|
21
|
+
/** Helper function to copy an `RNG` Isaac API class. */
|
|
22
|
+
export function copyRNG(rng: RNG): RNG {
|
|
23
|
+
if (!isRNG(rng)) {
|
|
24
|
+
error(
|
|
25
|
+
`Failed to copy a ${OBJECT_NAME} object since the provided object was not a userdata ${OBJECT_NAME} class.`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`Failed to serialize a ${OBJECT_NAME} object since the provided object was not a userdata ${OBJECT_NAME} class.`,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const seed = rng.GetSeed();
|
|
66
|
-
const rngTable = new LuaMap<string, unknown>();
|
|
67
|
-
rngTable.set("seed", seed);
|
|
68
|
-
rngTable.set(SerializationBrand.RNG, "");
|
|
69
|
-
return rngTable as SerializedRNG;
|
|
70
|
-
}
|
|
29
|
+
const seed = rng.GetSeed();
|
|
30
|
+
return newRNG(seed);
|
|
31
|
+
}
|
|
71
32
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
OBJECT_NAME,
|
|
82
|
-
...KEYS,
|
|
83
|
-
);
|
|
84
|
-
const seed = seedNumber as Seed;
|
|
85
|
-
return newRNG(seed);
|
|
86
|
-
}
|
|
33
|
+
/**
|
|
34
|
+
* Helper function to convert a `SerializedRNG` object to a normal `RNG` object. (This is used by
|
|
35
|
+
* the save data manager when reading data from the "save#.dat" file.)
|
|
36
|
+
*/
|
|
37
|
+
export function deserializeRNG(rng: SerializedRNG): RNG {
|
|
38
|
+
if (!isTable(rng)) {
|
|
39
|
+
error(
|
|
40
|
+
`Failed to deserialize a ${OBJECT_NAME} object since the provided object was not a Lua table.`,
|
|
41
|
+
);
|
|
87
42
|
}
|
|
43
|
+
|
|
44
|
+
const [seed] = getNumbersFromTable(
|
|
45
|
+
rng as LuaMap<string, unknown>,
|
|
46
|
+
OBJECT_NAME,
|
|
47
|
+
...KEYS,
|
|
48
|
+
);
|
|
49
|
+
return newRNG(seed as Seed);
|
|
88
50
|
}
|
|
89
51
|
|
|
90
52
|
/**
|
|
@@ -130,6 +92,24 @@ export function rngEquals(rng1: RNG, rng2: RNG): boolean {
|
|
|
130
92
|
return isaacAPIClassEquals(rng1, rng2, KEYS);
|
|
131
93
|
}
|
|
132
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Helper function to convert a `RNG` object to a `SerializedRNG` object. (This is used by the save
|
|
97
|
+
* data manager when writing data from the "save#.dat" file.)
|
|
98
|
+
*/
|
|
99
|
+
export function serializeRNG(rng: RNG): SerializedRNG {
|
|
100
|
+
if (!isRNG(rng)) {
|
|
101
|
+
error(
|
|
102
|
+
`Failed to serialize a ${OBJECT_NAME} object since the provided object was not a userdata ${OBJECT_NAME} class.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const seed = rng.GetSeed();
|
|
107
|
+
const rngTable = new LuaMap<string, unknown>();
|
|
108
|
+
rngTable.set("seed", seed);
|
|
109
|
+
rngTable.set(SerializationBrand.RNG, "");
|
|
110
|
+
return rngTable as SerializedRNG;
|
|
111
|
+
}
|
|
112
|
+
|
|
133
113
|
/**
|
|
134
114
|
* Helper function to iterate over the provided object and set the seed for all of the values that
|
|
135
115
|
* are RNG objects equal to a particular seed.
|
package/src/functions/rockAlt.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { game } from "../cachedClasses";
|
|
14
14
|
import { DISTANCE_OF_GRID_TILE } from "../constants";
|
|
15
15
|
import { RockAltType } from "../enums/RockAltType";
|
|
16
|
+
import { BACKDROP_TYPE_TO_ROCK_ALT_TYPE } from "../objects/backdropTypeToRockAltType";
|
|
16
17
|
import { spawnEffectWithSeed, spawnNPCWithSeed } from "./entitiesSpecific";
|
|
17
18
|
import { isCollectibleInItemPool } from "./itemPool";
|
|
18
19
|
import {
|
|
@@ -22,18 +23,58 @@ import {
|
|
|
22
23
|
spawnPillWithSeed,
|
|
23
24
|
spawnTrinketWithSeed,
|
|
24
25
|
} from "./pickupsSpecific";
|
|
26
|
+
import { fireProjectilesInCircle } from "./projectiles";
|
|
25
27
|
import { getRandom } from "./random";
|
|
26
28
|
import { getRandomSeed, isRNG, newRNG } from "./rng";
|
|
27
29
|
import { spawnCollectible } from "./spawnCollectible";
|
|
28
30
|
import { repeat } from "./utils";
|
|
29
31
|
import { getRandomVector } from "./vector";
|
|
30
32
|
|
|
33
|
+
const ROCK_ALT_CHANCES = {
|
|
34
|
+
Nothing: 0.68,
|
|
35
|
+
BasicDrop: 0.0967,
|
|
36
|
+
|
|
37
|
+
/** Also used for e.g. black hearts from skulls. */
|
|
38
|
+
Trinket: 0.025,
|
|
39
|
+
|
|
40
|
+
Collectible: 0.005,
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
const POLYP_PROJECTILE_SPEED = 10;
|
|
44
|
+
const POLYP_NUM_PROJECTILES = 6;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Helper function to get the alternate rock type (i.e. urn, mushroom, etc.) that the current room
|
|
48
|
+
* will have.
|
|
49
|
+
*
|
|
50
|
+
* The rock type is based on the backdrop of the room.
|
|
51
|
+
*
|
|
52
|
+
* For example, if you change the backdrop of the starting room of the run to `BackdropType.CAVES`,
|
|
53
|
+
* and then spawn `GridEntityType.ROCK_ALT`, it will be a mushroom instead of an urn. Additionally,
|
|
54
|
+
* if it is destroyed, it will generate mushroom-appropriate rewards.
|
|
55
|
+
*
|
|
56
|
+
* On the other hand, if an urn is spawned first before the backdrop is changed to
|
|
57
|
+
* `BackdropType.CAVES`, the graphic of the urn will not switch to a mushroom. However, when
|
|
58
|
+
* destroyed, the urn will still generate mushroom-appropriate rewards.
|
|
59
|
+
*/
|
|
60
|
+
export function getRockAltType(): RockAltType {
|
|
61
|
+
const room = game.GetRoom();
|
|
62
|
+
const backdropType = room.GetBackdropType();
|
|
63
|
+
|
|
64
|
+
return BACKDROP_TYPE_TO_ROCK_ALT_TYPE[backdropType];
|
|
65
|
+
}
|
|
66
|
+
|
|
31
67
|
/**
|
|
32
68
|
* Helper function for emulating what happens when a vanilla `GridEntityType.ROCK_ALT` grid entity
|
|
33
69
|
* breaks.
|
|
34
70
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
71
|
+
* Most of the time, this function will do nothing, similar to how most of the time, when an
|
|
72
|
+
* individual urn is destroyed, nothing will spawn.
|
|
73
|
+
*
|
|
74
|
+
* Note that in vanilla, trinkets will not spawn if they have already been removed from the trinket
|
|
75
|
+
* pool. This function cannot replicate that behavior because there is no way to check to see if a
|
|
76
|
+
* trinket is still in the pool. Thus, it will always have a chance to spawn the respective trinket
|
|
77
|
+
* (e.g. Swallowed Penny from urns).
|
|
37
78
|
*
|
|
38
79
|
* The logic in this function is based on the rewards listed on the wiki:
|
|
39
80
|
* https://bindingofisaacrebirth.fandom.com/wiki/Rocks
|
|
@@ -41,6 +82,10 @@ import { getRandomVector } from "./vector";
|
|
|
41
82
|
* @param position The place to spawn the reward.
|
|
42
83
|
* @param rockAltType The type of reward to spawn. For example, `RockAltType.URN` will have a chance
|
|
43
84
|
* at spawning coins and spiders.
|
|
85
|
+
* @param variant Optional. The variant of the grid entity to emulate. Default is 0, which
|
|
86
|
+
* corresponds to a "normal" grid entity or an empty bucket. This only matters when
|
|
87
|
+
* spawning the reward for buckets. (Empty buckets have different rewards than full
|
|
88
|
+
* buckets.)
|
|
44
89
|
* @param seedOrRNG Optional. The `Seed` or `RNG` object to use. If an `RNG` object is provided, the
|
|
45
90
|
* `RNG.Next` method will be called. Default is `getRandomSeed()`. Normally, you
|
|
46
91
|
* should pass the `InitSeed` of the grid entity that was broken.
|
|
@@ -49,6 +94,7 @@ import { getRandomVector } from "./vector";
|
|
|
49
94
|
export function spawnRockAltReward(
|
|
50
95
|
position: Vector,
|
|
51
96
|
rockAltType: RockAltType,
|
|
97
|
+
variant = 0,
|
|
52
98
|
seedOrRNG: Seed | RNG = getRandomSeed(),
|
|
53
99
|
): boolean {
|
|
54
100
|
const rng = isRNG(seedOrRNG) ? seedOrRNG : newRNG(seedOrRNG);
|
|
@@ -71,7 +117,7 @@ export function spawnRockAltReward(
|
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
case RockAltType.BUCKET: {
|
|
74
|
-
return spawnRockAltRewardBucket(position, rng);
|
|
120
|
+
return spawnRockAltRewardBucket(position, rng, variant);
|
|
75
121
|
}
|
|
76
122
|
}
|
|
77
123
|
}
|
|
@@ -80,14 +126,12 @@ function spawnRockAltRewardUrn(position: Vector, rng: RNG): boolean {
|
|
|
80
126
|
const chance = getRandom(rng);
|
|
81
127
|
let totalChance = 0;
|
|
82
128
|
|
|
83
|
-
|
|
84
|
-
totalChance += 0.68;
|
|
129
|
+
totalChance += ROCK_ALT_CHANCES.Nothing;
|
|
85
130
|
if (chance < totalChance) {
|
|
86
131
|
return false;
|
|
87
132
|
}
|
|
88
133
|
|
|
89
|
-
|
|
90
|
-
totalChance += 0.0944;
|
|
134
|
+
totalChance += ROCK_ALT_CHANCES.BasicDrop;
|
|
91
135
|
if (chance < totalChance) {
|
|
92
136
|
const numCoinsChance = getRandom(rng);
|
|
93
137
|
const numCoins = numCoinsChance < 0.5 ? 1 : 2;
|
|
@@ -100,15 +144,13 @@ function spawnRockAltRewardUrn(position: Vector, rng: RNG): boolean {
|
|
|
100
144
|
return true;
|
|
101
145
|
}
|
|
102
146
|
|
|
103
|
-
|
|
104
|
-
totalChance += 0.025;
|
|
147
|
+
totalChance += ROCK_ALT_CHANCES.Trinket;
|
|
105
148
|
if (chance < totalChance) {
|
|
106
149
|
spawnTrinketWithSeed(TrinketType.SWALLOWED_PENNY, position, rng);
|
|
107
150
|
return true;
|
|
108
151
|
}
|
|
109
152
|
|
|
110
|
-
|
|
111
|
-
totalChance += 0.005;
|
|
153
|
+
totalChance += ROCK_ALT_CHANCES.Collectible;
|
|
112
154
|
if (chance < totalChance) {
|
|
113
155
|
const stillInPools = isCollectibleInItemPool(
|
|
114
156
|
CollectibleType.QUARTER,
|
|
@@ -122,8 +164,7 @@ function spawnRockAltRewardUrn(position: Vector, rng: RNG): boolean {
|
|
|
122
164
|
return false;
|
|
123
165
|
}
|
|
124
166
|
|
|
125
|
-
//
|
|
126
|
-
// chance.)
|
|
167
|
+
// Since the detrimental effect is the final option, we don't need to check the chance.
|
|
127
168
|
const numSpidersChance = getRandom(rng);
|
|
128
169
|
const numSpiders = numSpidersChance < 0.5 ? 1 : 2;
|
|
129
170
|
const length = DISTANCE_OF_GRID_TILE * 3;
|
|
@@ -143,28 +184,24 @@ function spawnRockAltRewardMushroom(position: Vector, rng: RNG): boolean {
|
|
|
143
184
|
const chance = getRandom(rng);
|
|
144
185
|
let totalChance = 0;
|
|
145
186
|
|
|
146
|
-
|
|
147
|
-
totalChance += 0.68;
|
|
187
|
+
totalChance += ROCK_ALT_CHANCES.Nothing;
|
|
148
188
|
if (chance < totalChance) {
|
|
149
189
|
return false;
|
|
150
190
|
}
|
|
151
191
|
|
|
152
|
-
|
|
153
|
-
totalChance += 0.0982;
|
|
192
|
+
totalChance += ROCK_ALT_CHANCES.BasicDrop;
|
|
154
193
|
if (chance < totalChance) {
|
|
155
194
|
spawnPillWithSeed(PillColor.NULL, position, rng);
|
|
156
195
|
return true;
|
|
157
196
|
}
|
|
158
197
|
|
|
159
|
-
|
|
160
|
-
totalChance += 0.025;
|
|
198
|
+
totalChance += ROCK_ALT_CHANCES.Trinket;
|
|
161
199
|
if (chance < totalChance) {
|
|
162
200
|
spawnTrinketWithSeed(TrinketType.LIBERTY_CAP, position, rng);
|
|
163
201
|
return true;
|
|
164
202
|
}
|
|
165
203
|
|
|
166
|
-
|
|
167
|
-
totalChance += 0.005;
|
|
204
|
+
totalChance += ROCK_ALT_CHANCES.Collectible;
|
|
168
205
|
if (chance < totalChance) {
|
|
169
206
|
if (roomType === RoomType.SECRET) {
|
|
170
207
|
const wavyCapChance = getRandom(rng);
|
|
@@ -211,8 +248,7 @@ function spawnRockAltRewardMushroom(position: Vector, rng: RNG): boolean {
|
|
|
211
248
|
return false;
|
|
212
249
|
}
|
|
213
250
|
|
|
214
|
-
//
|
|
215
|
-
// the chance.)
|
|
251
|
+
// Since the detrimental effect is the final option, we don't need to check the chance.
|
|
216
252
|
spawnEffectWithSeed(EffectVariant.FART, 0, position, rng);
|
|
217
253
|
return true;
|
|
218
254
|
}
|
|
@@ -221,28 +257,24 @@ function spawnRockAltRewardSkull(position: Vector, rng: RNG): boolean {
|
|
|
221
257
|
const chance = getRandom(rng);
|
|
222
258
|
let totalChance = 0;
|
|
223
259
|
|
|
224
|
-
|
|
225
|
-
totalChance += 0.68;
|
|
260
|
+
totalChance += ROCK_ALT_CHANCES.Nothing;
|
|
226
261
|
if (chance < totalChance) {
|
|
227
262
|
return false;
|
|
228
263
|
}
|
|
229
264
|
|
|
230
|
-
|
|
231
|
-
totalChance += 0.095;
|
|
265
|
+
totalChance += ROCK_ALT_CHANCES.BasicDrop;
|
|
232
266
|
if (chance < totalChance) {
|
|
233
267
|
spawnCardWithSeed(Card.NULL, position, rng);
|
|
234
268
|
return true;
|
|
235
269
|
}
|
|
236
270
|
|
|
237
|
-
|
|
238
|
-
totalChance += 0.025;
|
|
271
|
+
totalChance += ROCK_ALT_CHANCES.Trinket;
|
|
239
272
|
if (chance < totalChance) {
|
|
240
273
|
spawnHeartWithSeed(HeartSubType.BLACK, position, rng);
|
|
241
274
|
return true;
|
|
242
275
|
}
|
|
243
276
|
|
|
244
|
-
|
|
245
|
-
totalChance += 0.005;
|
|
277
|
+
totalChance += ROCK_ALT_CHANCES.Collectible;
|
|
246
278
|
if (chance < totalChance) {
|
|
247
279
|
const ghostBabyStillInPools = isCollectibleInItemPool(
|
|
248
280
|
CollectibleType.GHOST_BABY,
|
|
@@ -275,31 +307,86 @@ function spawnRockAltRewardSkull(position: Vector, rng: RNG): boolean {
|
|
|
275
307
|
return false;
|
|
276
308
|
}
|
|
277
309
|
|
|
278
|
-
//
|
|
310
|
+
// Since the detrimental effect is the final option, we don't need to check the chance.
|
|
279
311
|
spawnNPCWithSeed(EntityType.HOST, 0, 0, position, rng);
|
|
280
312
|
return true;
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
function spawnRockAltRewardPolyp(
|
|
315
|
+
function spawnRockAltRewardPolyp(position: Vector, rng: RNG): boolean {
|
|
284
316
|
const chance = getRandom(rng);
|
|
285
317
|
let totalChance = 0;
|
|
286
318
|
|
|
287
|
-
|
|
288
|
-
totalChance += 0.68;
|
|
319
|
+
totalChance += ROCK_ALT_CHANCES.Nothing;
|
|
289
320
|
if (chance < totalChance) {
|
|
290
321
|
return false;
|
|
291
322
|
}
|
|
292
323
|
|
|
293
|
-
|
|
294
|
-
|
|
324
|
+
totalChance += ROCK_ALT_CHANCES.BasicDrop;
|
|
325
|
+
if (chance < totalChance) {
|
|
326
|
+
spawnHeartWithSeed(HeartSubType.NULL, position, rng);
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
totalChance += ROCK_ALT_CHANCES.Trinket;
|
|
331
|
+
if (chance < totalChance) {
|
|
332
|
+
spawnTrinketWithSeed(TrinketType.UMBILICAL_CORD, position, rng);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
totalChance += ROCK_ALT_CHANCES.Collectible;
|
|
337
|
+
if (chance < totalChance) {
|
|
338
|
+
const placentaStillInPools = isCollectibleInItemPool(
|
|
339
|
+
CollectibleType.PLACENTA,
|
|
340
|
+
ItemPoolType.BOSS,
|
|
341
|
+
);
|
|
342
|
+
const bloodClotStillInPools = isCollectibleInItemPool(
|
|
343
|
+
CollectibleType.BLOOD_CLOT,
|
|
344
|
+
ItemPoolType.BOSS,
|
|
345
|
+
);
|
|
346
|
+
if (bloodClotStillInPools && placentaStillInPools) {
|
|
347
|
+
const collectibleChance = getRandom(rng);
|
|
348
|
+
const collectibleType =
|
|
349
|
+
collectibleChance < 0.5
|
|
350
|
+
? CollectibleType.PLACENTA // 218
|
|
351
|
+
: CollectibleType.BLOOD_CLOT; // 254
|
|
352
|
+
spawnCollectible(collectibleType, position, rng);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (bloodClotStillInPools) {
|
|
357
|
+
spawnCollectible(CollectibleType.MINI_MUSH, position, rng);
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (placentaStillInPools) {
|
|
362
|
+
spawnCollectible(CollectibleType.MAGIC_MUSHROOM, position, rng);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Since the detrimental effect is the final option, we don't need to check the chance.
|
|
370
|
+
spawnEffectWithSeed(EffectVariant.CREEP_RED, 0, position, rng);
|
|
371
|
+
fireProjectilesInCircle(
|
|
372
|
+
undefined,
|
|
373
|
+
position,
|
|
374
|
+
POLYP_PROJECTILE_SPEED,
|
|
375
|
+
POLYP_NUM_PROJECTILES,
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
return true;
|
|
295
379
|
}
|
|
296
380
|
|
|
297
|
-
function spawnRockAltRewardBucket(
|
|
381
|
+
function spawnRockAltRewardBucket(
|
|
382
|
+
_position: Vector,
|
|
383
|
+
rng: RNG,
|
|
384
|
+
_variant: int,
|
|
385
|
+
): boolean {
|
|
298
386
|
const chance = getRandom(rng);
|
|
299
387
|
let totalChance = 0;
|
|
300
388
|
|
|
301
|
-
|
|
302
|
-
totalChance += 0.68;
|
|
389
|
+
totalChance += ROCK_ALT_CHANCES.Nothing;
|
|
303
390
|
if (chance < totalChance) {
|
|
304
391
|
return false;
|
|
305
392
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CollectibleType, ItemPoolType } from "isaac-typescript-definitions";
|
|
2
|
+
import { isCollectibleInItemPool } from "./itemPool";
|
|
2
3
|
import { anyPlayerHasCollectible } from "./players";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -23,5 +24,5 @@ export function isCollectibleUnlocked(
|
|
|
23
24
|
return true;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
return
|
|
27
|
+
return isCollectibleInItemPool(collectibleType, itemPoolType);
|
|
27
28
|
}
|