isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
360 lines (359 loc) • 17.5 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CustomGridEntities = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedClasses_1 = require("../../../core/cachedClasses");
const decorators_1 = require("../../../decorators");
const ISCFeature_1 = require("../../../enums/ISCFeature");
const ModCallbackCustom_1 = require("../../../enums/ModCallbackCustom");
const gridEntities_1 = require("../../../functions/gridEntities");
const players_1 = require("../../../functions/players");
const roomData_1 = require("../../../functions/roomData");
const types_1 = require("../../../functions/types");
const utils_1 = require("../../../functions/utils");
const vector_1 = require("../../../functions/vector");
const DefaultMap_1 = require("../../DefaultMap");
const Feature_1 = require("../../private/Feature");
const v = {
level: {
/** Indexed by room list index and grid index. */
customGridEntities: new DefaultMap_1.DefaultMap(() => new Map()),
},
room: {
genericPropPtrHashes: new Set(),
manuallyUsingShovel: false,
},
};
class CustomGridEntities extends Feature_1.Feature {
/** @internal */
v = v;
runInNFrames;
/** @internal */
constructor(runInNFrames) {
super();
this.featuresUsed = [ISCFeature_1.ISCFeature.RUN_IN_N_FRAMES];
this.callbacksUsed = [
// 23
[
isaac_typescript_definitions_1.ModCallback.PRE_USE_ITEM,
this.preUseItemWeNeedToGoDeeper,
[isaac_typescript_definitions_1.CollectibleType.WE_NEED_TO_GO_DEEPER],
],
];
this.customCallbacksUsed = [
[ModCallbackCustom_1.ModCallbackCustom.POST_NEW_ROOM_REORDERED, this.postNewRoomReordered],
];
this.runInNFrames = runInNFrames;
}
// ModCallback.PRE_USE_ITEM (23)
// CollectibleType.WE_NEED_TO_GO_DEEPER (84)
preUseItemWeNeedToGoDeeper = (_collectibleType, _rng, player, _useFlags, _activeSlot, _customVarData) => {
// If a player uses We Need to Go Deeper on top of a custom grid entity, then they will always
// get a crawlspace, due to how custom grids are implemented with decorations. Thus, remove the
// custom grid entity to prevent this from happening if needed.
const room = cachedClasses_1.game.GetRoom();
const roomListIndex = (0, roomData_1.getRoomListIndex)();
const roomCustomGridEntities = v.level.customGridEntities.get(roomListIndex);
if (roomCustomGridEntities === undefined) {
return undefined;
}
const gridIndex = room.GetGridIndex(player.Position);
const customGridEntity = roomCustomGridEntities.get(gridIndex);
if (customGridEntity === undefined) {
return undefined;
}
// If the custom grid entity has collision, then the player should not be able to be standing on
// top of it.
if (customGridEntity.gridCollisionClass !== isaac_typescript_definitions_1.GridCollisionClass.NONE) {
return undefined;
}
(0, gridEntities_1.removeGridEntity)(customGridEntity.gridIndex, false);
const entityPtr = EntityPtr(player);
this.runInNFrames.runNextGameFrame(() => {
const futurePlayer = (0, players_1.getPlayerFromPtr)(entityPtr);
if (futurePlayer === undefined) {
return;
}
v.room.manuallyUsingShovel = true;
futurePlayer.UseActiveItem(isaac_typescript_definitions_1.CollectibleType.WE_NEED_TO_GO_DEEPER);
v.room.manuallyUsingShovel = false;
});
// Cancel the original effect.
return true;
};
// ModCallbackCustom.POST_NEW_ROOM_REORDERED
postNewRoomReordered = () => {
// When we re-enter a room, the graphics for any custom entities will be reverted back to that
// of a normal decoration. Thus, we must re-apply the anm2.
const roomListIndex = (0, roomData_1.getRoomListIndex)();
const roomCustomGridEntities = v.level.customGridEntities.get(roomListIndex);
if (roomCustomGridEntities === undefined) {
return;
}
const room = cachedClasses_1.game.GetRoom();
for (const [gridIndex, data] of roomCustomGridEntities) {
const decoration = room.GetGridEntity(gridIndex);
if (decoration === undefined) {
roomCustomGridEntities.delete(gridIndex);
continue;
}
if (data.anm2Path !== undefined) {
const sprite = decoration.GetSprite();
sprite.Load(data.anm2Path, true);
const animationToPlay = data.defaultAnimation ?? sprite.GetDefaultAnimation();
sprite.Play(animationToPlay, true);
}
}
};
/**
* Helper function to spawn a custom grid entity. Custom grid entities are persistent in that they
* will reappear if the player leaves and re-enters the room. (It will be manually respawned in
* the `POST_NEW_ROOM` callback.)
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.CUSTOM_GRID_ENTITIES`.
*
* Custom grid entities are built on top of real grid entities. You can use any existing grid
* entity type as a base. For example, if you want to create a custom rock that would be breakable
* like a normal rock, then you should specify `GridEntityType.ROCK` as the base grid entity type.
*
* Once a custom grid entity is spawned, you can take advantage of the custom grid callbacks such
* as `POST_GRID_ENTITY_CUSTOM_UPDATE`. Note that the "normal" grid entities callbacks will not
* fire for custom entities. For example, if you had a custom grid entity based on
* `GridEntityType.ROCK`, and you also had a subscription to the `POST_GRID_ENTITY_UPDATE`
* callback, the callback would only fire for normal rocks and not the custom entity.
*
* Custom grid entities are an IsaacScript feature because the vanilla game does not support any
* custom grid entities.
*
* For example, this would be code to create a custom rock called a "Silver Rock" that produces a
* dime when destroyed:
*
* ```ts
* // This is local to the mod and can safely overlap with the values of `GridEntityType` (or
* // values chosen by other mods).
* const GridEntityTypeCustom = {
* SILVER_ROCK: 0 as GridEntityType,
* } as const;
*
* // This is copied from "gfx/grid/grid_rock.anm2" with some tweaks to make it look special.
* const SILVER_ROCK_ANM2_PATH = "gfx/grid/grid_rock_silver.anm2";
*
* export function silverRockInit(mod: ModUpgraded): void {
* mod.AddCallbackCustom(
* ModCallbackCustom.POST_GRID_ENTITY_CUSTOM_BROKEN,
* postGridEntityCustomBrokenSilverRock,
* GridEntityTypeCustom.SILVER_ROCK,
* );
* }
*
* function postGridEntityCustomBrokenSilverRock(gridEntity: GridEntity) {
* spawnCoin(CoinSubType.DIME, gridEntity.Position);
* }
*
* export function spawnSilverRock(mod: ModUpgraded, gridIndex: int): GridEntity {
* return mod.spawnCustomGridEntity(
* GridEntityTypeCustom.SILVER_ROCK,
* gridIndex,
* undefined,
* SILVER_ROCK_ANM2_PATH,
* undefined,
* GridEntityType.ROCK,
* );
* }
* ```
*
* @param gridEntityTypeCustom An integer that identifies what kind of grid entity you are
* creating. It should correspond to a local enum value created in
* your mod. The integer can be any unique value and will not
* correspond to the actual grid entity type used. (This integer is
* used in the various custom grid entity callbacks.)
* @param gridIndexOrPosition The grid index or position in the room that you want to spawn the
* grid entity at. If a position is specified, the closest grid index
* will be used.
* @param gridCollisionClass Optional. The collision class that you want the custom grid entity to
* have. If not specified, the grid collision class from the base grid
* entity will be used.
* @param anm2Path Optional. The path to the ANM2 file to use for the sprite. If not specified,
* the normal sprite from the base grid entity will be used.
* @param defaultAnimation Optional. The name of the animation to play after the sprite is
* initialized and after the player re-enters a room with this grid entity
* in it. If not specified, the default animation in the anm2 will be
* used.
* @param baseGridEntityType Optional. The type of the grid entity to use as a "base" for this
* custom grid entity. Default is `GridEntityType.DECORATION`.
* @param baseGridEntityVariant Optional. The variant of the grid entity to use as a "base" for
* this custom grid entity. Default is 0.
* @public
*/
spawnCustomGridEntity(gridEntityTypeCustom, gridIndexOrPosition, gridCollisionClass, anm2Path, defaultAnimation, baseGridEntityType = isaac_typescript_definitions_1.GridEntityType.DECORATION, baseGridEntityVariant = 0) {
const room = cachedClasses_1.game.GetRoom();
const roomListIndex = (0, roomData_1.getRoomListIndex)();
const gridIndex = (0, vector_1.isVector)(gridIndexOrPosition)
? room.GetGridIndex(gridIndexOrPosition)
: gridIndexOrPosition;
const customGridEntity = (0, gridEntities_1.spawnGridEntityWithVariant)(baseGridEntityType, baseGridEntityVariant, gridIndexOrPosition);
(0, utils_1.assertDefined)(customGridEntity, "Failed to spawn a custom grid entity.");
if (gridCollisionClass !== undefined) {
customGridEntity.CollisionClass = gridCollisionClass;
}
if (anm2Path !== undefined) {
const sprite = customGridEntity.GetSprite();
sprite.Load(anm2Path, true);
const animationToPlay = defaultAnimation ?? sprite.GetDefaultAnimation();
sprite.Play(animationToPlay, true);
}
const customGridEntityData = {
gridEntityTypeCustom,
roomListIndex,
gridIndex,
anm2Path,
defaultAnimation,
gridCollisionClass,
};
const roomCustomGridEntities = v.level.customGridEntities.getAndSetDefault(roomListIndex);
roomCustomGridEntities.set(gridIndex, customGridEntityData);
return customGridEntity;
}
/**
* Helper function to remove a custom grid entity created by the `spawnCustomGrid` function.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.CUSTOM_GRID_ENTITIES`.
*
* @param gridIndexOrPositionOrGridEntity You can specify the custom grid entity to remove by
* providing the grid index, the room position, or the grid entity
* itself.
* @param updateRoom Optional. Whether to update the room after the grid entity is removed.
* Default is true. This is generally a good idea because if the room is not
* updated, you will be unable to spawn another grid entity on the same tile
* until a frame has passed. However, doing this is expensive, since it involves
* a call to `Isaac.GetRoomEntities`, so set it to false if you need to run this
* function multiple times.
* @returns The grid entity that was removed. Returns undefined if no grid entity was found at the
* given location or if the given grid entity was not a custom grid entity.
* @public
*/
removeCustomGridEntity(gridIndexOrPositionOrGridEntity, updateRoom = true) {
const room = cachedClasses_1.game.GetRoom();
const roomListIndex = (0, roomData_1.getRoomListIndex)();
let decoration;
if (typeof gridIndexOrPositionOrGridEntity === "number") {
const gridIndex = gridIndexOrPositionOrGridEntity;
const gridEntity = room.GetGridEntity(gridIndex);
if (gridEntity === undefined) {
return undefined;
}
decoration = gridEntity;
}
else if ((0, vector_1.isVector)(gridIndexOrPositionOrGridEntity)) {
const position = gridIndexOrPositionOrGridEntity;
const gridEntity = room.GetGridEntityFromPos(position);
if (gridEntity === undefined) {
return undefined;
}
decoration = gridEntity;
}
else {
decoration = gridIndexOrPositionOrGridEntity;
}
const gridIndex = decoration.GetGridIndex();
const roomCustomGridEntities = v.level.customGridEntities.getAndSetDefault(roomListIndex);
const exists = roomCustomGridEntities.has(gridIndex);
if (!exists) {
return undefined;
}
roomCustomGridEntities.delete(gridIndex);
(0, gridEntities_1.removeGridEntity)(decoration, updateRoom);
return decoration;
}
/**
* Helper function to get the custom grid entities in the current room. Returns an array of tuples
* containing the raw decoration grid entity and the associated entity data.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.CUSTOM_GRID_ENTITIES`.
*
* @public
*/
getCustomGridEntities() {
const roomListIndex = (0, roomData_1.getRoomListIndex)();
const roomCustomGridEntities = v.level.customGridEntities.get(roomListIndex);
if (roomCustomGridEntities === undefined) {
return [];
}
const room = cachedClasses_1.game.GetRoom();
const customGridEntities = [];
for (const [gridIndex, data] of roomCustomGridEntities) {
const gridEntity = room.GetGridEntity(gridIndex);
if (gridEntity !== undefined) {
customGridEntities.push({ gridEntity, data });
}
}
return customGridEntities;
}
/**
* Helper function to get the custom `GridEntityType` from a `GridEntity` or grid index. Returns
* undefined if the provided `GridEntity` is not a custom grid entity, or if there was not a grid
* entity on the provided grid index.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.CUSTOM_GRID_ENTITIES`.
*
* @public
*/
getCustomGridEntityType(gridEntityOrGridIndex) {
if (!this.initialized) {
return undefined;
}
const gridIndex = (0, types_1.isInteger)(gridEntityOrGridIndex)
? gridEntityOrGridIndex
: gridEntityOrGridIndex.GetGridIndex();
const roomListIndex = (0, roomData_1.getRoomListIndex)();
const roomCustomGridEntities = v.level.customGridEntities.get(roomListIndex);
if (roomCustomGridEntities === undefined) {
return undefined;
}
for (const [_gridIndex, data] of roomCustomGridEntities) {
if (data.gridIndex === gridIndex) {
return data.gridEntityTypeCustom;
}
}
return undefined;
}
/**
* Helper function to check if a `GridEntity` is a custom grid entity or if a grid index has a
* custom grid entity.
*
* In order to use this function, you must upgrade your mod with
* `ISCFeature.CUSTOM_GRID_ENTITIES`.
*
* @public
*/
isCustomGridEntity(gridEntityOrGridIndex) {
const gridEntityTypeCustom = this.getCustomGridEntityType(gridEntityOrGridIndex);
return gridEntityTypeCustom !== undefined;
}
}
exports.CustomGridEntities = CustomGridEntities;
__decorate([
decorators_1.Exported
], CustomGridEntities.prototype, "spawnCustomGridEntity", null);
__decorate([
decorators_1.Exported
], CustomGridEntities.prototype, "removeCustomGridEntity", null);
__decorate([
decorators_1.Exported
], CustomGridEntities.prototype, "getCustomGridEntities", null);
__decorate([
decorators_1.Exported
], CustomGridEntities.prototype, "getCustomGridEntityType", null);
__decorate([
decorators_1.Exported
], CustomGridEntities.prototype, "isCustomGridEntity", null);