isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
331 lines (287 loc) • 9.13 kB
text/typescript
import {
GameStateFlag,
LevelStage,
StageType,
} from "isaac-typescript-definitions";
import { game } from "../../../core/cachedClasses";
import { Exported } from "../../../decorators";
import { ModCallbackCustom } from "../../../enums/ModCallbackCustom";
import { getNextStage, getNextStageType } from "../../../functions/nextStage";
import {
calculateStageType,
onRepentanceStage,
} from "../../../functions/stage";
import type { StageHistoryEntry } from "../../../interfaces/StageHistoryEntry";
import { Feature } from "../../private/Feature";
const v = {
run: {
stageHistory: [] as StageHistoryEntry[],
},
};
export class StageHistory extends Feature {
/** @internal */
public override v = v;
/** @internal */
constructor() {
super();
this.customCallbacksUsed = [
[ModCallbackCustom.POST_NEW_LEVEL_REORDERED, this.postNewLevelReordered],
];
}
// ModCallbackCustom.POST_NEW_LEVEL_REORDERED
private readonly postNewLevelReordered = () => {
const level = game.GetLevel();
const stage = level.GetStage();
const stageType = level.GetStageType();
v.run.stageHistory.push({ stage, stageType });
};
/**
* Helper function to get the stage type that a trapdoor or heaven door would take the player to,
* based on the current stage, room, and game state flags.
*
* This function accounts for the previous floors that a player has visited thus far on the run so
* that the next stage type can be properly calculated on The Ascent (which makes it unlike the
* `getNextStageType` function).
*
* In order to use this function, you must upgrade your mod with `ISCFeature.STAGE_HISTORY`.
*
* @param upwards Whether the player should go up to Cathedral in the case of being on Womb 2.
* Default is false.
* @public
*/
public getNextStageTypeWithHistory(upwards = false): StageType {
const backwardsPath = game.GetStateFlag(GameStateFlag.BACKWARDS_PATH);
if (!backwardsPath) {
return getNextStageType(upwards);
}
const level = game.GetLevel();
const stage = level.GetStage();
const repentanceStage = onRepentanceStage();
const visitedDownpour1 = this.hasVisitedStage(
LevelStage.BASEMENT_1,
StageType.REPENTANCE,
);
const visitedDross1 = this.hasVisitedStage(
LevelStage.BASEMENT_1,
StageType.REPENTANCE_B,
);
const visitedDownpour2 = this.hasVisitedStage(
LevelStage.BASEMENT_2,
StageType.REPENTANCE,
);
const visitedDross2 = this.hasVisitedStage(
LevelStage.BASEMENT_2,
StageType.REPENTANCE_B,
);
const visitedMines1 = this.hasVisitedStage(
LevelStage.CAVES_1,
StageType.REPENTANCE,
);
const visitedAshpit1 = this.hasVisitedStage(
LevelStage.CAVES_1,
StageType.REPENTANCE_B,
);
const visitedMines2 = this.hasVisitedStage(
LevelStage.DEPTHS_2,
StageType.REPENTANCE,
);
const visitedAshpit2 = this.hasVisitedStage(
LevelStage.DEPTHS_2,
StageType.REPENTANCE_B,
);
if (stage === LevelStage.BASEMENT_2 && repentanceStage) {
if (visitedDownpour1) {
return StageType.REPENTANCE;
}
if (visitedDross1) {
return StageType.REPENTANCE_B;
}
}
if (stage === LevelStage.CAVES_1 && repentanceStage) {
if (visitedDownpour2) {
return StageType.REPENTANCE;
}
if (visitedDross2) {
return StageType.REPENTANCE_B;
}
}
if (stage === LevelStage.CAVES_2 && !repentanceStage) {
if (visitedDownpour2) {
return StageType.REPENTANCE;
}
if (visitedDross2) {
return StageType.REPENTANCE_B;
}
}
if (stage === LevelStage.CAVES_2 && repentanceStage) {
if (visitedMines1) {
return StageType.REPENTANCE;
}
if (visitedAshpit1) {
return StageType.REPENTANCE_B;
}
}
if (stage === LevelStage.DEPTHS_2 && !repentanceStage) {
if (visitedAshpit2) {
return StageType.REPENTANCE_B;
}
if (visitedMines2) {
return StageType.REPENTANCE;
}
}
const nextStage = this.getNextStageWithHistory();
return calculateStageType(nextStage);
}
/**
* Helper function to get the stage that a trapdoor or heaven door would take the player to, based
* on the current stage, room, and game state flags.
*
* This function accounts for the previous floors that a player has visited thus far on the run so
* that the next stage can be properly calculated on The Ascent (which makes it unlike the
* `getNextStage` function).
*
* In order to use this function, you must upgrade your mod with `ISCFeature.STAGE_HISTORY`.
*/
public getNextStageWithHistory(): LevelStage {
const backwardsPath = game.GetStateFlag(GameStateFlag.BACKWARDS_PATH);
if (!backwardsPath) {
return getNextStage();
}
const level = game.GetLevel();
const stage = level.GetStage();
const repentanceStage = onRepentanceStage();
const visitedDownpour1 = this.hasVisitedStage(
LevelStage.BASEMENT_1,
StageType.REPENTANCE,
);
const visitedDross1 = this.hasVisitedStage(
LevelStage.BASEMENT_1,
StageType.REPENTANCE_B,
);
const visitedDownpour2 = this.hasVisitedStage(
LevelStage.BASEMENT_2,
StageType.REPENTANCE,
);
const visitedDross2 = this.hasVisitedStage(
LevelStage.BASEMENT_2,
StageType.REPENTANCE_B,
);
const visitedMines1 = this.hasVisitedStage(
LevelStage.CAVES_1,
StageType.REPENTANCE,
);
const visitedAshpit1 = this.hasVisitedStage(
LevelStage.CAVES_1,
StageType.REPENTANCE_B,
);
const visitedMines2 = this.hasVisitedStage(
LevelStage.DEPTHS_2,
StageType.REPENTANCE,
);
const visitedAshpit2 = this.hasVisitedStage(
LevelStage.DEPTHS_2,
StageType.REPENTANCE_B,
);
if (stage === LevelStage.BASEMENT_1) {
if (repentanceStage) {
// From Downpour 1 to Basement 1.
return LevelStage.BASEMENT_1;
}
// From Basement 1 to Home.
return LevelStage.HOME;
}
if (stage === LevelStage.BASEMENT_2) {
if (repentanceStage) {
if (visitedDownpour1 || visitedDross1) {
// From Downpour 2 to Downpour 1.
return LevelStage.BASEMENT_1;
}
// From Downpour 2 to Basement 2.
return LevelStage.BASEMENT_2;
}
// From Basement 2 to Basement 1.
return LevelStage.BASEMENT_1;
}
if (stage === LevelStage.CAVES_1) {
if (repentanceStage) {
if (visitedDownpour2 || visitedDross2) {
// From Mines 1 to Downpour 1.
return LevelStage.BASEMENT_2;
}
// From Mines 1 to Caves 1.
return LevelStage.CAVES_1;
}
// From Caves 1 to Basement 2.
return LevelStage.BASEMENT_2;
}
if (stage === LevelStage.CAVES_2) {
if (repentanceStage) {
if (visitedMines1 || visitedAshpit1) {
// From Mines 2 to Mines 1.
return LevelStage.CAVES_1;
}
// From Mines 2 to Caves 2.
return LevelStage.CAVES_2;
}
// From Caves 2 to Caves 1.
return LevelStage.CAVES_1;
}
if (stage === LevelStage.DEPTHS_1) {
if (repentanceStage) {
if (visitedMines2 || visitedAshpit2) {
// From Mausoleum 1 to Mines 2.
return LevelStage.CAVES_2;
}
// From Mausoleum 1 to Depths 1.
return LevelStage.DEPTHS_1;
}
// From Depths 1 to Caves 2.
return LevelStage.CAVES_2;
}
if (stage === LevelStage.DEPTHS_2) {
if (repentanceStage) {
// From Mausoleum 2 to Depths 2.
return LevelStage.DEPTHS_2;
}
// From Depths 2 to Depths 1.
return LevelStage.DEPTHS_1;
}
return stage - 1;
}
/**
* Helper function to get all of the stages that a player has visited thus far on this run.
*
* In order to use this function, you must upgrade your mod with `ISCFeature.STAGE_HISTORY`.
*
* @public
*/
public getStageHistory(): readonly StageHistoryEntry[] {
return v.run.stageHistory;
}
/**
* Helper function to check if a player has previous visited a particular stage (or stage + stage
* type combination) on this run.
*
* In order to use this function, you must upgrade your mod with `ISCFeature.STAGE_HISTORY`.
*
* @param stage The stage to check for.
* @param stageType Optional. If provided, will check for a specific stage and stage type
* combination.
*/
public hasVisitedStage(stage: LevelStage, stageType?: StageType): boolean {
if (stageType === undefined) {
return v.run.stageHistory.some(
(stageHistoryEntry) => stageHistoryEntry.stage === stage,
);
}
return v.run.stageHistory.some(
(stageHistoryEntry) =>
stageHistoryEntry.stage === stage
&& stageHistoryEntry.stageType === stageType,
);
}
}