isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
346 lines (299 loc) • 10.5 kB
text/typescript
import type { DamageFlag } from "isaac-typescript-definitions";
import {
CacheFlag,
CollectibleType,
LevelCurse,
ModCallback,
TearVariant,
} from "isaac-typescript-definitions";
import { Exported } from "../../../decorators";
import { ModCallbackCustom } from "../../../enums/ModCallbackCustom";
import { isVanillaConsoleCommand } from "../../../functions/console";
import { addFlag, bitFlags } from "../../../functions/flag";
import { logError } from "../../../functions/log";
import { getMapPartialMatch } from "../../../functions/string";
import { assertDefined } from "../../../functions/utils";
import { Feature } from "../../private/Feature";
import * as commands from "./extraConsoleCommands/commands";
import { v } from "./extraConsoleCommands/v";
// eslint-disable-next-line @typescript-eslint/naming-convention
declare let __ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE:
| ExtraConsoleCommands
| undefined;
/**
* When you enable this feature, many custom commands will be added to the in-game console. See the
* [dedicated command list](/isaacscript-common/features/ExtraConsoleCommandsList) for more
* information about them.
*
* Note that in order to avoid conflicts, if two or more mods enable this feature, then the first
* loaded one will control all of the command logic. When this occurs, a global variable of
* `__ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE` will be created and will automatically be
* used by the non-main instances. For this reason, if you use multiple mods with
* `isaacscript-common` and a custom command from the standard library is not working properly, then
* you might need to get another mod author to update their version of `isaacscript-common`.
*/
export class ExtraConsoleCommands extends Feature {
/** @internal */
public override v = v;
private readonly isMainFeature: boolean;
private readonly commandFunctionMap = new Map<
string,
(params: string) => void
>();
/** @internal */
constructor() {
super();
// Only one instance of this feature can be instantiated across all mods.
this.isMainFeature =
__ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE === undefined;
if (!this.isMainFeature) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-this-alias, consistent-this
__ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE = this;
this.callbacksUsed = [
// 1
[ModCallback.POST_UPDATE, this.postUpdate],
// 8
[
ModCallback.EVALUATE_CACHE,
this.evaluateCacheDamage,
[CacheFlag.DAMAGE], // 1 << 0
],
// 8
[
ModCallback.EVALUATE_CACHE,
this.evaluateCacheFireDelay,
[CacheFlag.FIRE_DELAY], // 1 << 1
],
// 8
[
ModCallback.EVALUATE_CACHE,
this.evaluateCacheSpeed,
[CacheFlag.SPEED], // 1 << 4
],
// 8
[
ModCallback.EVALUATE_CACHE,
this.evaluateCacheFlying,
[CacheFlag.FLYING], // 1 << 7
],
// 12
[ModCallback.POST_CURSE_EVAL, this.postCurseEval],
// 22
[ModCallback.EXECUTE_CMD, this.executeCmd],
// 61
[ModCallback.POST_FIRE_TEAR, this.postFireTear],
];
this.customCallbacksUsed = [
[ModCallbackCustom.ENTITY_TAKE_DMG_PLAYER, this.entityTakeDmgPlayer],
];
for (const [funcName, func] of Object.entries(commands)) {
this.commandFunctionMap.set(funcName, func);
}
}
// ModCallback.POST_UPDATE (1)
private readonly postUpdate = () => {
if (v.persistent.spamBloodRights) {
const player = Isaac.GetPlayer();
player.UseActiveItem(CollectibleType.BLOOD_RIGHTS);
}
};
// ModCallback.EVALUATE_CACHE (8)
// CacheFlag.DAMAGE (1 << 0)
private readonly evaluateCacheDamage = (player: EntityPlayer) => {
if (v.persistent.damage) {
player.Damage = v.persistent.damageAmount;
}
};
// ModCallback.EVALUATE_CACHE (8)
// CacheFlag.FIRE_DELAY (1 << 1)
private readonly evaluateCacheFireDelay = (player: EntityPlayer) => {
if (v.persistent.tears) {
player.FireDelay = v.persistent.tearsAmount;
}
};
// ModCallback.EVALUATE_CACHE (8)
// CacheFlag.SPEED (1 << 4)
private readonly evaluateCacheSpeed = (player: EntityPlayer) => {
if (v.persistent.speed) {
player.MoveSpeed = v.persistent.speedAmount;
}
};
// ModCallback.EVALUATE_CACHE (8)
// CacheFlag.FLYING (1 << 7)
private readonly evaluateCacheFlying = (player: EntityPlayer) => {
if (v.persistent.flight) {
player.CanFly = true;
}
};
// ModCallback.POST_CURSE_EVAL (12)
private readonly postCurseEval = (
curses: BitFlags<LevelCurse>,
): BitFlags<LevelCurse> | LevelCurse | undefined => {
if (v.persistent.disableCurses) {
return bitFlags(LevelCurse.NONE);
}
let newCurses = curses;
// 1
if (v.persistent.darkness) {
newCurses = addFlag(newCurses, LevelCurse.DARKNESS);
}
// 2
if (v.persistent.labyrinth) {
newCurses = addFlag(newCurses, LevelCurse.LABYRINTH);
}
// 3
if (v.persistent.lost) {
newCurses = addFlag(newCurses, LevelCurse.LOST);
}
// 4
if (v.persistent.unknown) {
newCurses = addFlag(newCurses, LevelCurse.UNKNOWN);
}
// 5
if (v.persistent.cursed) {
newCurses = addFlag(newCurses, LevelCurse.CURSED);
}
// 6
if (v.persistent.maze) {
newCurses = addFlag(newCurses, LevelCurse.MAZE);
}
// 7
if (v.persistent.blind) {
newCurses = addFlag(newCurses, LevelCurse.BLIND);
}
// 8
if (v.persistent.giant) {
newCurses = addFlag(newCurses, LevelCurse.GIANT);
}
return curses === newCurses ? undefined : newCurses;
};
// ModCallback.EXECUTE_CMD (22)
private readonly executeCmd = (command: string, params: string) => {
const resultTuple = getMapPartialMatch(command, this.commandFunctionMap);
if (resultTuple === undefined) {
// We opt to not print an error message because a non-IsaacScript mod may have configured a
// custom console command.
return;
}
const [commandName, commandFunction] = resultTuple;
print(`Command: ${commandName}`);
commandFunction(params);
};
// ModCallback.POST_FIRE_TEAR (61)
private readonly postFireTear = (tear: EntityTear) => {
if (v.persistent.chaosCardTears) {
tear.ChangeVariant(TearVariant.CHAOS_CARD);
}
};
// ModCallbackCustom.ENTITY_TAKE_DMG_PLAYER
private readonly entityTakeDmgPlayer = (
_player: EntityPlayer,
_damageAmount: float,
_damageFlags: BitFlags<DamageFlag>,
_damageSource: EntityRef,
_damageCountdownFrames: int,
) => {
if (v.persistent.spamBloodRights) {
return false;
}
return undefined;
};
/**
* Helper function to add a custom console command.
*
* The standard library comes with [many existing console
* commands](/isaacscript-common/features/ExtraConsoleCommandsList) that are useful for debugging,
* but you can also add your own commands that are useful for your particular mod. It's easier to
* add commands to the existing command system than to add your own logic manually to the
* `EXECUTE_CMD` callback.
*
* This function is intended to be called when your mod is first loading.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.EXTRA_CONSOLE_COMMANDS`.
*
* @public
*/
public addConsoleCommand(
commandName: string,
commandFunction: (params: string) => void,
): void {
if (!this.isMainFeature) {
assertDefined(
__ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE,
"Failed to find the non-main isaacscript-common extra console commands feature in the global variable.",
);
__ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE.addConsoleCommand(
commandName,
commandFunction,
);
return;
}
if (isVanillaConsoleCommand(commandName)) {
logError(
`Failed to add a new console command of "${commandName}" because that name already belongs to a vanilla command. You must pick a non-colliding name.`,
);
return;
}
if (this.commandFunctionMap.has(commandName)) {
logError(
`Failed to add a new console command of "${commandName}" because there is already an existing custom command by that name. If you want to overwrite a command from the standard library, you can use the "removeExtraConsoleCommand" function.`,
);
return;
}
this.commandFunctionMap.set(commandName, commandFunction);
}
/**
* Helper function to remove a custom console command.
*
* The standard library comes with [many existing console
* commands](/isaacscript-common/features/ExtraConsoleCommandsList) that are useful for debugging.
* If you want to disable one of them, use this function.
*
* This function is intended to be called when your mod is first loading.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.EXTRA_CONSOLE_COMMANDS`.
*
* @public
*/
public removeConsoleCommand(commandName: string): void {
if (!this.isMainFeature) {
assertDefined(
__ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE,
"Failed to find the non-main isaacscript-common extra console commands feature in the global variable.",
);
__ISAACSCRIPT_COMMON_EXTRA_CONSOLE_COMMANDS_FEATURE.removeConsoleCommand(
commandName,
);
return;
}
if (!this.commandFunctionMap.has(commandName)) {
error(
`Failed to remove the console command of "${commandName}", since it does not already exist in the command map.`,
);
}
this.commandFunctionMap.delete(commandName);
}
/**
* Helper function to remove all custom console commands.
*
* The standard library comes with [many existing console
* commands](/isaacscript-common/features/ExtraConsoleCommandsList) that are useful for debugging.
* If you want to disable all of them after this feature has already been initialized, use this
* function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.EXTRA_CONSOLE_COMMANDS`.
*
* @public
*/
public removeAllConsoleCommands(): void {
this.commandFunctionMap.clear();
}
}