Skip to content

Commit

Permalink
fix(common): runInNFrames logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Zamiell committed Jul 10, 2022
1 parent f307c42 commit 66e8cf2
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 60 deletions.
6 changes: 5 additions & 1 deletion packages/docs/docs/main/change-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ This page lists the changes to the IsaacScript framework.
## July 12th, 2022

- Breaking:
- `addRoomClearCharge` now has an argument of `bigRoomDoubleCharge` instead of `ignoreBigRoomDoubleCharge`, so you will need to invert the boolean.
- `addRoomClearCharge` now has an argument of `bigRoomDoubleCharge` (instead of the old argument of `ignoreBigRoomDoubleCharge`), so you will need to invert the boolean.
- `arrayRemove` will now only remove the first matching element (instead of every matching element). Use `arrayRemoveAll` for that behavior instead.
- All of the `spawn` helper functions now have the option to pass an RNG object instead of a seed.
- `addRoomClearCharge` and `addRoomClearChargeToSlot` now take an optional argument of `playSoundEffect`.
- Added the following helper functions:
Expand All @@ -24,6 +25,9 @@ This page lists the changes to the IsaacScript framework.
- `getGridEntitiesExcept`
- `spawnCustomGrid`
- `removeCustomGrid`
- `addCharge`
- `arrayRemoveAll`
- `arrayRemoveAllInPlace`

## July 5th, 2022

Expand Down
39 changes: 39 additions & 0 deletions packages/isaacscript-common/src/features/customStage/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { LevelStage } from "isaac-typescript-definitions";
import { CustomStageData } from "../../interfaces/CustomStageData";
import { JSONRoom } from "../../interfaces/JSONRoom";
import { customStages } from "./v";

/**
* Helper function to register a new custom stage with the IsaacScript standard library stage
* system.
*/
export function registerCustomStage(
name: string,
baseStage: LevelStage,
jsonRooms: JSONRoom[] | readonly JSONRoom[],
): void {
if (customStages.has(name)) {
error(
`Failed to register a custom stage of "${name}" since there is already a custom stage registered by that name.`,
);
}

if (jsonRooms.length === 0) {
error(
`Failed to register a custom stage of "${name}" since the provided JSON room array was empty.`,
);
}

const customStageData: CustomStageData = {
name,
baseStage,
};
customStages.set(name, customStageData);
}

/**
* Helper function to warp to a custom stage/level.
*
* Custom stages/levels must first be registered with the `registerCustomStage` function.
*/
export function setCustomStage(_name: string): void {}
3 changes: 3 additions & 0 deletions packages/isaacscript-common/src/features/customStage/v.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { CustomStageData } from "../../interfaces/CustomStageData";

export const customStages = new Map<string, CustomStageData>();
10 changes: 7 additions & 3 deletions packages/isaacscript-common/src/features/deployJSONRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ function respawnPersistentEntities() {
* what the function is doing. Default is false.
*/
export function deployJSONRoom(
jsonRoom: JSONRoom,
jsonRoom: JSONRoom | Readonly<JSONRoom>,
seedOrRNG: Seed | RNG = getRandomSeed(),
verbose = false,
): void {
Expand Down Expand Up @@ -293,7 +293,7 @@ export function deployJSONRoom(
* what the function is doing. Default is false.
*/
export function deployRandomJSONRoom(
jsonRooms: JSONRoom[],
jsonRooms: JSONRoom[] | readonly JSONRoom[],
seedOrRNG: Seed | RNG = getRandomSeed(),
verbose = false,
): void {
Expand Down Expand Up @@ -422,7 +422,11 @@ function fillRoomWithDecorations() {
}
}

function spawnAllEntities(jsonRoom: JSONRoom, rng: RNG, verbose = false) {
function spawnAllEntities(
jsonRoom: JSONRoom | Readonly<JSONRoom>,
rng: RNG,
verbose = false,
) {
let shouldUnclearRoom = false;

for (const jsonSpawn of jsonRoom.spawn) {
Expand Down
58 changes: 15 additions & 43 deletions packages/isaacscript-common/src/features/runInNFrames.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ModCallback } from "isaac-typescript-definitions";
import { game } from "../cachedClasses";
import { errorIfFeaturesNotInitialized } from "../featuresInitialized";
import { arrayRemoveIndexInPlace } from "../functions/array";
import { arrayRemoveInPlace } from "../functions/array";
import { saveDataManager } from "./saveDataManager/exports";

const FEATURE_NAME = "runInNFrames";
Expand All @@ -20,12 +20,6 @@ type IntervalFunctionTuple = [
numIntervalFrames: int,
];

type FiringFunctionTuple = [
index: int,
func: (() => void) | (() => boolean),
numIntervalFrames: int | undefined,
];

const v = {
run: {
queuedGameFunctionTuples: [] as QueuedFunctionTuple[],
Expand Down Expand Up @@ -73,63 +67,41 @@ function checkExecuteQueuedFunctions(
frameCount: int,
functionTuples: QueuedFunctionTuple[],
) {
const firingFunctions = getFunctionsThatShouldFireOnThisFrame(
frameCount,
functionTuples,
const firingFunctions = functionTuples.filter(
([frameCountToFire]) => frameCount >= frameCountToFire,
);

for (const [i, func] of firingFunctions) {
for (const tuple of firingFunctions) {
const [_frameCountToFire, func] = tuple;
func();
arrayRemoveIndexInPlace(functionTuples, i);
arrayRemoveInPlace(functionTuples, tuple);
}
}

function checkExecuteIntervalFunctions(
frameCount: int,
functionTuples: IntervalFunctionTuple[],
) {
const firingFunctions = getFunctionsThatShouldFireOnThisFrame(
frameCount,
functionTuples,
const firingFunctions = functionTuples.filter(
([frameCountToFire]) => frameCount >= frameCountToFire,
);

for (const [i, func, numIntervalFrames] of firingFunctions) {
for (const tuple of firingFunctions) {
const [_frameCountToFire, func, numIntervalFrames] = tuple;
const returnValue = func();

arrayRemoveIndexInPlace(functionTuples, i);
arrayRemoveInPlace(functionTuples, tuple);

// Queue the next interval (as long as the function did not return false).
if (numIntervalFrames !== undefined && returnValue !== false) {
if (returnValue) {
const nextFireFrame = frameCount + numIntervalFrames;
const tuple: IntervalFunctionTuple = [
const newTuple: IntervalFunctionTuple = [
nextFireFrame,
func as () => boolean,
numIntervalFrames,
];
functionTuples.push(tuple);
}
}
}

function getFunctionsThatShouldFireOnThisFrame(
frameCount: int,
functionTuples: QueuedFunctionTuple[] | IntervalFunctionTuple[],
): readonly FiringFunctionTuple[] {
const firingFunctionTuples: FiringFunctionTuple[] = [];
functionTuples.forEach((functionTuple, i) => {
const [frameCountToFire, func, numIntervalFrames] = functionTuple;

if (frameCount >= frameCountToFire) {
const firingFunctionTuple: FiringFunctionTuple = [
i,
func,
numIntervalFrames,
];
firingFunctionTuples.push(firingFunctionTuple);
functionTuples.push(newTuple);
}
});

return firingFunctionTuples;
}
}

/**
Expand Down
72 changes: 64 additions & 8 deletions packages/isaacscript-common/src/functions/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,82 @@ export function arrayEquals<T>(
* array.
*
* This function is variadic, meaning that you can specify N arguments to remove N elements.
*
* If there is more than one matching element in the array, this function will only remove the first
* matching element. If you want to remove all of the elements, use the `arrayRemoveAll` function
* instead.
*/
export function arrayRemove<T>(
originalArray: T[] | readonly T[],
...elementsToRemove: T[]
): T[] {
const elementsToRemoveSet = new Set(elementsToRemove);
const array = copyArray(originalArray);
arrayRemoveInPlace(array, ...elementsToRemove);
return array;
}

const array: T[] = [];
for (const element of originalArray) {
if (!elementsToRemoveSet.has(element)) {
array.push(element);
}
/**
* Shallow copies and removes the specified element(s) from the array. Returns the copied array. If
* the specified element(s) are not found in the array, it will simply return a shallow copy of the
* array.
*
* This function is variadic, meaning that you can specify N arguments to remove N elements.
*
* If there is more than one matching element in the array, this function will remove every matching
* element. If you want to only remove the first matching element, use the `arrayRemove` function
* instead.
*/
export function arrayRemoveAll<T>(
originalArray: T[] | readonly T[],
...elementsToRemove: T[]
): T[] {
const array = copyArray(originalArray);
arrayRemoveAllInPlace(array, ...elementsToRemove);
return array;
}

/**
* Removes all of the specified element(s) from the array. If the specified element(s) are not found
* in the array, this function will do nothing.
*
* This function is variadic, meaning that you can specify N arguments to remove N elements.
*
* If there is more than one matching element in the array, this function will remove every matching
* element. If you want to only remove the first matching element, use the `arrayRemoveInPlace`
* function instead.
*
* @returns True if one or more elements were removed, false otherwise.
*/
export function arrayRemoveAllInPlace<T>(
array: T[],
...elementsToRemove: T[]
): boolean {
let removedOneOrMoreElements = false;
for (const element of elementsToRemove) {
let index: number;
do {
index = array.indexOf(element);
if (index > -1) {
removedOneOrMoreElements = true;
array.splice(index, 1);
}
} while (index > -1);
}

return array;
return removedOneOrMoreElements;
}

/**
* Removes the specified element(s) from the array. If the specified element(s) are not found in the
* array, this function will do nothing. Returns true if one or more elements were removed.
* array, this function will do nothing.
*
* This function is variadic, meaning that you can specify N arguments to remove N elements.
*
* If there is more than one matching element in the array, this function will only remove the first
* matching element. If you want to remove all of the elements, use the `arrayRemoveAllInPlace`
* function instead.
*
* @returns True if one or more elements were removed, false otherwise.
*/
export function arrayRemoveInPlace<T>(
array: T[],
Expand Down Expand Up @@ -95,6 +149,8 @@ export function arrayRemoveIndex<T>(
* removed.
*
* This function is variadic, meaning that you can specify N arguments to remove N elements.
*
* @returns Whether or not any array elements were removed.
*/
export function arrayRemoveIndexInPlace<T>(
array: T[],
Expand Down
12 changes: 7 additions & 5 deletions packages/isaacscript-common/src/functions/jsonRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getRandomFloat } from "./random";
import { getRandomSeed } from "./rng";

export function getJSONRoomOfVariant(
jsonRooms: JSONRoom[],
jsonRooms: JSONRoom[] | readonly JSONRoom[],
variant: int,
): JSONRoom | undefined {
const jsonRoomsOfVariant = jsonRooms.filter((jsonRoom) => {
Expand All @@ -30,7 +30,7 @@ export function getJSONRoomOfVariant(
}

export function getJSONRoomsOfSubType(
jsonRooms: JSONRoom[],
jsonRooms: JSONRoom[] | readonly JSONRoom[],
subType: int,
): JSONRoom[] {
return jsonRooms.filter((jsonRoom) => {
Expand All @@ -48,7 +48,7 @@ export function getJSONRoomsOfSubType(
* https://stackoverflow.com/questions/1761626/weighted-random-numbers
*/
export function getRandomJSONRoom(
jsonRooms: JSONRoom[],
jsonRooms: JSONRoom[] | readonly JSONRoom[],
seedOrRNG: Seed | RNG = getRandomSeed(),
verbose = false,
): JSONRoom {
Expand All @@ -65,7 +65,9 @@ export function getRandomJSONRoom(
return getJSONRoomWithChosenWeight(jsonRooms, chosenWeight);
}

function getTotalWeightOfJSONRooms(jsonRooms: JSONRoom[]) {
function getTotalWeightOfJSONRooms(
jsonRooms: JSONRoom[] | readonly JSONRoom[],
) {
const weights = jsonRooms.map((jsonRoom) => {
const roomWeightString = jsonRoom.$.weight;
const roomWeight = tonumber(roomWeightString);
Expand All @@ -79,7 +81,7 @@ function getTotalWeightOfJSONRooms(jsonRooms: JSONRoom[]) {
}

function getJSONRoomWithChosenWeight(
jsonRooms: JSONRoom[],
jsonRooms: JSONRoom[] | readonly JSONRoom[],
chosenWeight: float,
) {
for (const jsonRoom of jsonRooms) {
Expand Down
6 changes: 6 additions & 0 deletions packages/isaacscript-common/src/interfaces/CustomStageData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { LevelStage } from "isaac-typescript-definitions";

export interface CustomStageData {
name: string;
baseStage: LevelStage;
}
3 changes: 3 additions & 0 deletions packages/isaacscript-common/typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"src/enums/SerializationType.ts",
"src/enums/SlotDestructionType.ts",
"src/features/collectibleItemPoolType.ts",
"src/features/customGridEntity.ts",
"src/features/debugDisplay/exports.ts",
"src/features/deployJSONRoom.ts",
"src/features/disableAllSound.ts",
Expand Down Expand Up @@ -130,6 +131,8 @@
"src/functions/utils.ts",
"src/functions/vector.ts",
"src/interfaces/ChargeBarSprites.ts",
"src/interfaces/CustomGridEntityData.ts",
"src/interfaces/CustomStageData.ts",
"src/interfaces/JSONDoor.ts",
"src/interfaces/JSONEntity.ts",
"src/interfaces/JSONRoom.ts",
Expand Down

0 comments on commit 66e8cf2

Please sign in to comment.