isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
604 lines (603 loc) • 26.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addPlayerHealthType = addPlayerHealthType;
exports.canPickEternalHearts = canPickEternalHearts;
exports.doesPlayerHaveAllBlackHearts = doesPlayerHaveAllBlackHearts;
exports.doesPlayerHaveAllSoulHearts = doesPlayerHaveAllSoulHearts;
exports.getPlayerAvailableHeartSlots = getPlayerAvailableHeartSlots;
exports.getPlayerBlackHearts = getPlayerBlackHearts;
exports.getPlayerHealth = getPlayerHealth;
exports.getPlayerHealthType = getPlayerHealthType;
exports.getPlayerHearts = getPlayerHearts;
exports.getPlayerLastHeart = getPlayerLastHeart;
exports.getPlayerMaxHeartContainers = getPlayerMaxHeartContainers;
exports.getPlayerSoulHearts = getPlayerSoulHearts;
exports.getTaintedMagdaleneNonTemporaryMaxHearts = getTaintedMagdaleneNonTemporaryMaxHearts;
exports.newPlayerHealth = newPlayerHealth;
exports.playerConvertBlackHeartsToSoulHearts = playerConvertBlackHeartsToSoulHearts;
exports.playerConvertSoulHeartsToBlackHearts = playerConvertSoulHeartsToBlackHearts;
exports.playerHasHealthLeft = playerHasHealthLeft;
exports.removeAllPlayerHealth = removeAllPlayerHealth;
exports.setPlayerHealth = setPlayerHealth;
exports.wouldDamageTaintedMagdaleneNonTemporaryHeartContainers = wouldDamageTaintedMagdaleneNonTemporaryHeartContainers;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const constants_1 = require("../core/constants");
const HealthType_1 = require("../enums/HealthType");
const bitwise_1 = require("./bitwise");
const characters_1 = require("./characters");
const charge_1 = require("./charge");
const playerCollectibles_1 = require("./playerCollectibles");
const players_1 = require("./players");
const utils_1 = require("./utils");
function addPlayerHealthType(player, healthType, numHearts) {
switch (healthType) {
case HealthType_1.HealthType.RED: {
player.AddHearts(numHearts);
break;
}
case HealthType_1.HealthType.SOUL: {
player.AddSoulHearts(numHearts);
break;
}
case HealthType_1.HealthType.ETERNAL: {
player.AddEternalHearts(numHearts);
break;
}
case HealthType_1.HealthType.BLACK: {
player.AddBlackHearts(numHearts);
break;
}
case HealthType_1.HealthType.GOLDEN: {
player.AddGoldenHearts(numHearts);
break;
}
case HealthType_1.HealthType.BONE: {
player.AddBoneHearts(numHearts);
break;
}
case HealthType_1.HealthType.ROTTEN: {
player.AddRottenHearts(numHearts);
break;
}
case HealthType_1.HealthType.BROKEN: {
player.AddBrokenHearts(numHearts);
break;
}
case HealthType_1.HealthType.MAX_HEARTS: {
player.AddMaxHearts(numHearts, false);
break;
}
}
}
/**
* Helper function to see if the provided player can pick up an eternal heart. (If a player already
* has an eternal heart and full heart containers, they are not able to pick up any additional
* eternal hearts.)
*
* This function's name matches the existing `EntityPlayer` methods.
*/
function canPickEternalHearts(player) {
const eternalHearts = player.GetEternalHearts();
const maxHearts = player.GetMaxHearts();
const heartLimit = player.GetHeartLimit();
return eternalHearts === 0 || maxHearts !== heartLimit;
}
/**
* Returns whether all of the player's soul-heart-type hearts are black hearts.
*
* Note that this function does not consider red heart containers.
*
* For example:
*
* - If the player has one black heart, this function would return true.
* - If the player has one soul heart and two black hearts, this function would return false.
* - If the player has no black hearts, this function will return false.
* - If the player has one red heart container and three black hearts, this function would return
* true.
*/
function doesPlayerHaveAllBlackHearts(player) {
const soulHearts = getPlayerSoulHearts(player);
const blackHearts = getPlayerBlackHearts(player);
return blackHearts > 0 && soulHearts === 0;
}
/**
* Returns whether all of the player's soul-heart-type hearts are soul hearts.
*
* Note that this function does not consider red heart containers.
*
* For example:
*
* - If the player has two soul hearts and one black heart, this function would return false.
* - If the player has no soul hearts, this function will return false.
* - If the player has one red heart container and three soul hearts, this function would return
* true.
*/
function doesPlayerHaveAllSoulHearts(player) {
const soulHearts = getPlayerSoulHearts(player);
const blackHearts = getPlayerBlackHearts(player);
return soulHearts > 0 && blackHearts === 0;
}
/**
* Returns the number of slots that the player has remaining for new heart containers, accounting
* for broken hearts. For example, if the player is Judas and has 1 red heart containers and 2 full
* soul hearts and 3 broken hearts, then this function would return 6 (i.e. 12 - 1 - 2 - 3).
*/
function getPlayerAvailableHeartSlots(player) {
const maxHeartContainers = getPlayerMaxHeartContainers(player);
const effectiveMaxHearts = player.GetEffectiveMaxHearts();
const normalAndBoneHeartContainers = effectiveMaxHearts / 2;
const soulHearts = player.GetSoulHearts();
const soulHeartContainers = Math.ceil(soulHearts / 2);
const totalHeartContainers = normalAndBoneHeartContainers + soulHeartContainers;
const brokenHearts = player.GetBrokenHearts();
const totalOccupiedHeartSlots = totalHeartContainers + brokenHearts;
return maxHeartContainers - totalOccupiedHeartSlots;
}
/**
* Returns the number of black hearts that the player has, excluding any soul hearts. For example,
* if the player has one full black heart, one full soul heart, and one half black heart, this
* function returns 3.
*
* This is different from the `EntityPlayer.GetBlackHearts` method, since that returns a bitmask.
*/
function getPlayerBlackHearts(player) {
const blackHeartsBitmask = player.GetBlackHearts();
const blackHeartBits = (0, bitwise_1.countSetBits)(blackHeartsBitmask);
return blackHeartBits * 2;
}
/**
* Helper function to get an object representing the player's health. You can use this in
* combination with the `setPlayerHealth` function to restore the player's health back to a certain
* configuration at a later time.
*
* This is based on the `REVEL.StoreHealth` function in the Revelations mod.
*/
function getPlayerHealth(player) {
const character = player.GetPlayerType();
let maxHearts = player.GetMaxHearts();
let hearts = getPlayerHearts(player); // We use the helper function to remove rotten hearts
let soulHearts = player.GetSoulHearts();
let boneHearts = player.GetBoneHearts();
const goldenHearts = player.GetGoldenHearts();
const eternalHearts = player.GetEternalHearts();
const rottenHearts = player.GetRottenHearts();
const brokenHearts = player.GetBrokenHearts();
const subPlayer = player.GetSubPlayer();
const soulCharges = player.GetEffectiveSoulCharge();
const bloodCharges = player.GetEffectiveBloodCharge();
// The Forgotten and The Soul has special health, so we need to account for this.
if (character === isaac_typescript_definitions_1.PlayerType.FORGOTTEN && subPlayer !== undefined) {
// The Forgotten does not have red heart containers.
maxHearts = boneHearts * 2;
boneHearts = 0;
// The Forgotten will always have 0 soul hearts; we need to get the soul heart amount from the
// sub player.
soulHearts = subPlayer.GetSoulHearts();
}
else if (character === isaac_typescript_definitions_1.PlayerType.SOUL && subPlayer !== undefined) {
// The Soul will always have 0 bone hearts; we need to get the bone heart amount from the sub
// player. We need to store it as "maxHearts" instead of "boneHearts".
maxHearts = subPlayer.GetBoneHearts() * 2;
hearts = subPlayer.GetHearts();
}
// This is the number of individual hearts shown in the HUD, minus heart containers.
const extraHearts = Math.ceil(soulHearts / 2) + boneHearts;
// Since bone hearts can be inserted anywhere between soul hearts, we need a separate counter to
// track which soul heart we're currently at.
let currentSoulHeart = 0;
const soulHeartTypes = [];
for (let i = 0; i < extraHearts; i++) {
let isBoneHeart = player.IsBoneHeart(i);
if (character === isaac_typescript_definitions_1.PlayerType.FORGOTTEN && subPlayer !== undefined) {
isBoneHeart = subPlayer.IsBoneHeart(i);
}
if (isBoneHeart) {
soulHeartTypes.push(isaac_typescript_definitions_1.HeartSubType.BONE);
}
else {
// We need to add 1 here because only the second half of a black heart is considered black.
let isBlackHeart = player.IsBlackHeart(currentSoulHeart + 1);
if (character === isaac_typescript_definitions_1.PlayerType.FORGOTTEN && subPlayer !== undefined) {
isBlackHeart = subPlayer.IsBlackHeart(currentSoulHeart + 1);
}
if (isBlackHeart) {
soulHeartTypes.push(isaac_typescript_definitions_1.HeartSubType.BLACK);
}
else {
soulHeartTypes.push(isaac_typescript_definitions_1.HeartSubType.SOUL);
}
// Move to the next heart.
currentSoulHeart += 2;
}
}
return {
maxHearts,
hearts,
eternalHearts,
soulHearts,
boneHearts,
goldenHearts,
rottenHearts,
brokenHearts,
soulCharges,
bloodCharges,
soulHeartTypes,
};
}
function getPlayerHealthType(player, healthType) {
switch (healthType) {
// 5.10.1
case HealthType_1.HealthType.RED: {
// We use the standard library helper function since the `EntityPlayer.GetHearts` method
// returns a value that includes rotten hearts.
return getPlayerHearts(player);
}
// 5.10.3
case HealthType_1.HealthType.SOUL: {
// We use the standard library helper function since the `EntityPlayer.GetSoulHearts` method
// returns a value that includes black hearts.
return getPlayerSoulHearts(player);
}
// 5.10.4
case HealthType_1.HealthType.ETERNAL: {
return player.GetEternalHearts();
}
// 5.10.6
case HealthType_1.HealthType.BLACK: {
// We use the standard library helper function since the `EntityPlayer.GetBlackHearts` method
// returns a bit mask.
return getPlayerBlackHearts(player);
}
// 5.10.7
case HealthType_1.HealthType.GOLDEN: {
return player.GetGoldenHearts();
}
// 5.10.11
case HealthType_1.HealthType.BONE: {
return player.GetBoneHearts();
}
// 5.10.12
case HealthType_1.HealthType.ROTTEN: {
return player.GetRottenHearts();
}
case HealthType_1.HealthType.BROKEN: {
return player.GetBrokenHearts();
}
case HealthType_1.HealthType.MAX_HEARTS: {
return player.GetMaxHearts();
}
}
}
/**
* Returns the number of red hearts that the player has, excluding any rotten hearts. For example,
* if the player has one full black heart, one full soul heart, and one half black heart, this
* function returns 3.
*
* This is different from the `EntityPlayer.GetHearts` method, since that returns a value that
* includes rotten hearts.
*/
function getPlayerHearts(player) {
const rottenHearts = player.GetRottenHearts();
const hearts = player.GetHearts();
return hearts - rottenHearts * 2;
}
/**
* Helper function that returns the type of the rightmost heart. This does not including golden
* hearts or broken hearts, since they cannot be damaged directly.
*/
function getPlayerLastHeart(player) {
const hearts = player.GetHearts();
const effectiveMaxHearts = player.GetEffectiveMaxHearts();
const soulHearts = player.GetSoulHearts();
const blackHearts = player.GetBlackHearts();
const eternalHearts = player.GetEternalHearts();
const boneHearts = player.GetBoneHearts();
const rottenHearts = player.GetRottenHearts();
const soulHeartSlots = soulHearts / 2;
const lastHeartIndex = boneHearts + soulHeartSlots - 1;
const isLastHeartBone = player.IsBoneHeart(lastHeartIndex);
if (isLastHeartBone) {
const isLastContainerEmpty = hearts <= effectiveMaxHearts - 2;
if (isLastContainerEmpty) {
return HealthType_1.HealthType.BONE;
}
if (rottenHearts > 0) {
return HealthType_1.HealthType.ROTTEN;
}
if (eternalHearts > 0) {
return HealthType_1.HealthType.ETERNAL;
}
return HealthType_1.HealthType.RED;
}
if (soulHearts > 0) {
const numBits = (0, bitwise_1.getNumBitsOfN)(blackHearts);
const finalBit = (0, bitwise_1.getKBitOfN)(numBits - 1, blackHearts);
const isBlack = finalBit === 1;
if (isBlack) {
return HealthType_1.HealthType.BLACK;
}
// If it is not a black heart, it must be a soul heart.
return HealthType_1.HealthType.SOUL;
}
if (eternalHearts > 0) {
return HealthType_1.HealthType.ETERNAL;
}
if (rottenHearts > 0) {
return HealthType_1.HealthType.ROTTEN;
}
return HealthType_1.HealthType.RED;
}
/**
* Returns the maximum heart containers that the provided player can have. Normally, this is 12, but
* it can change depending on the character (e.g. Keeper) and other things (e.g. Mother's Kiss).
* This function does not account for Broken Hearts; use the `getPlayerAvailableHeartSlots` helper
* function for that.
*/
function getPlayerMaxHeartContainers(player) {
const character = player.GetPlayerType();
const characterMaxHeartContainers = (0, characters_1.getCharacterMaxHeartContainers)(character);
// 1
// Magdalene can increase her maximum heart containers with Birthright.
if (character === isaac_typescript_definitions_1.PlayerType.MAGDALENE
&& player.HasCollectible(isaac_typescript_definitions_1.CollectibleType.BIRTHRIGHT)) {
const extraMaxHeartContainersFromBirthright = 6;
return characterMaxHeartContainers + extraMaxHeartContainersFromBirthright;
}
// 14, 33
// Keeper and Tainted Keeper can increase their coin containers with Mother's Kiss and Greed's
// Gullet.
if ((0, players_1.isKeeper)(player)) {
const numMothersKisses = player.GetTrinketMultiplier(isaac_typescript_definitions_1.TrinketType.MOTHERS_KISS);
const hasGreedsGullet = player.HasCollectible(isaac_typescript_definitions_1.CollectibleType.GREEDS_GULLET);
const coins = player.GetNumCoins();
const greedsGulletCoinContainers = hasGreedsGullet
? Math.floor(coins / 25)
: 0;
return (characterMaxHeartContainers
+ numMothersKisses
+ greedsGulletCoinContainers);
}
return characterMaxHeartContainers;
}
/**
* Returns the number of soul hearts that the player has, excluding any black hearts. For example,
* if the player has one full black heart, one full soul heart, and one half black heart, this
* function returns 2.
*
* This is different from the `EntityPlayer.GetSoulHearts` method, since that returns the combined
* number of soul hearts and black hearts.
*/
function getPlayerSoulHearts(player) {
const soulHearts = player.GetSoulHearts();
const blackHearts = getPlayerBlackHearts(player);
return soulHearts - blackHearts;
}
/**
* Helper function to determine how many heart containers that Tainted Magdalene has that will not
* be automatically depleted over time. By default, this is 2, but this function will return 4 so
* that it is consistent with the `player.GetHearts` and `player.GetMaxHearts` methods.
*
* If Tainted Magdalene has Birthright, she will gained an additional non-temporary heart container.
*
* This function does not validate whether the provided player is Tainted Magdalene; that should be
* accomplished before invoking this function.
*/
function getTaintedMagdaleneNonTemporaryMaxHearts(player) {
const maxHearts = player.GetMaxHearts();
const hasBirthright = player.HasCollectible(isaac_typescript_definitions_1.CollectibleType.BIRTHRIGHT);
const maxNonTemporaryMaxHearts = hasBirthright ? 6 : 4;
return Math.min(maxHearts, maxNonTemporaryMaxHearts);
}
/** Returns a `PlayerHealth` object with all zeros. */
function newPlayerHealth() {
return {
maxHearts: 0,
hearts: 0,
eternalHearts: 0,
soulHearts: 0,
boneHearts: 0,
goldenHearts: 0,
rottenHearts: 0,
brokenHearts: 0,
soulCharges: 0,
bloodCharges: 0,
soulHeartTypes: [],
};
}
/**
* Helper function to remove all of a player's black hearts and add the corresponding amount of soul
* hearts.
*/
function playerConvertBlackHeartsToSoulHearts(player) {
const playerHealth = getPlayerHealth(player);
removeAllPlayerHealth(player);
const newSoulHeartTypes = playerHealth.soulHeartTypes.map((soulHeartType) => soulHeartType === isaac_typescript_definitions_1.HeartSubType.BLACK ? isaac_typescript_definitions_1.HeartSubType.SOUL : soulHeartType);
const playerHealthWithSoulHearts = {
...playerHealth,
soulHeartTypes: newSoulHeartTypes,
};
setPlayerHealth(player, playerHealthWithSoulHearts);
}
/**
* Helper function to remove all of a player's soul hearts and add the corresponding amount of black
* hearts.
*/
function playerConvertSoulHeartsToBlackHearts(player) {
const playerHealth = getPlayerHealth(player);
removeAllPlayerHealth(player);
const newSoulHeartTypes = playerHealth.soulHeartTypes.map((soulHeartType) => soulHeartType === isaac_typescript_definitions_1.HeartSubType.SOUL ? isaac_typescript_definitions_1.HeartSubType.BLACK : soulHeartType);
const playerHealthWithBlackHearts = {
...playerHealth,
soulHeartTypes: newSoulHeartTypes,
};
setPlayerHealth(player, playerHealthWithBlackHearts);
}
/**
* Helper function to see if the player is out of health.
*
* Specifically, this function will return false if the player has 0 red hearts, 0 soul/black
* hearts, and 0 bone hearts.
*/
function playerHasHealthLeft(player) {
const hearts = player.GetHearts();
const soulHearts = player.GetSoulHearts();
const boneHearts = player.GetBoneHearts();
return hearts > 0 || soulHearts > 0 || boneHearts > 0;
}
function removeAllPlayerHealth(player) {
const goldenHearts = player.GetGoldenHearts();
const eternalHearts = player.GetEternalHearts();
const boneHearts = player.GetBoneHearts();
const brokenHearts = player.GetBrokenHearts();
// To avoid bugs, we have to remove the exact amount of certain types of hearts. We remove Golden
// Hearts first so that they don't break.
player.AddGoldenHearts(goldenHearts * -1);
player.AddEternalHearts(eternalHearts * -1);
player.AddBoneHearts(boneHearts * -1);
player.AddBrokenHearts(brokenHearts * -1);
player.AddMaxHearts(constants_1.MAX_PLAYER_HEART_CONTAINERS * -2, true);
player.AddSoulHearts(constants_1.MAX_PLAYER_HEART_CONTAINERS * -2);
// If we are The Soul, the `EntityPlayer.AddBoneHearts` method will not remove Forgotten's bone
// hearts, so we need to explicitly handle this.
if ((0, players_1.isCharacter)(player, isaac_typescript_definitions_1.PlayerType.SOUL)) {
const forgotten = player.GetSubPlayer();
if (forgotten !== undefined) {
const forgottenBoneHearts = forgotten.GetBoneHearts();
forgotten.AddBoneHearts(forgottenBoneHearts * -1);
}
}
}
/**
* Helper function to set a player's health to a specific state. You can use this in combination
* with the `getPlayerHealth` function to restore the player's health back to a certain
* configuration at a later time.
*
* Based on the `REVEL.LoadHealth` function in the Revelations mod.
*/
function setPlayerHealth(player, playerHealth) {
const character = player.GetPlayerType();
const subPlayer = player.GetSubPlayer();
// Before we add or remove any health, we have to take away Alabaster Box, if present. (Removing
// soul hearts from the player will remove Alabaster Box charges.)
const alabasterBoxDescriptions = [];
const alabasterBoxActiveSlots = (0, playerCollectibles_1.getActiveItemSlots)(player, isaac_typescript_definitions_1.CollectibleType.ALABASTER_BOX);
for (const activeSlot of alabasterBoxActiveSlots) {
const totalCharge = (0, charge_1.getTotalCharge)(player, activeSlot);
(0, playerCollectibles_1.setActiveItem)(player, isaac_typescript_definitions_1.CollectibleType.NULL, activeSlot);
alabasterBoxDescriptions.push({ activeSlot, totalCharge });
}
removeAllPlayerHealth(player);
// Add the red heart containers.
if (character === isaac_typescript_definitions_1.PlayerType.SOUL && subPlayer !== undefined) {
// Adding health to The Soul is a special case.
subPlayer.AddMaxHearts(playerHealth.maxHearts, false);
}
else {
player.AddMaxHearts(playerHealth.maxHearts, false);
}
// Add the eternal hearts.
player.AddEternalHearts(playerHealth.eternalHearts);
// Add the soul / black / bone hearts.
let soulHeartsRemaining = playerHealth.soulHearts;
for (const [i, soulHeartType] of playerHealth.soulHeartTypes.entries()) {
const isHalf = playerHealth.soulHearts + playerHealth.boneHearts * 2 < (i + 1) * 2;
let addAmount = 2;
if (isHalf
|| soulHeartType === isaac_typescript_definitions_1.HeartSubType.BONE
|| soulHeartsRemaining < 2) {
// Fix the bug where a half soul heart to the left of a bone heart will be treated as a full
// soul heart.
addAmount = 1;
}
switch (soulHeartType) {
case isaac_typescript_definitions_1.HeartSubType.SOUL: {
player.AddSoulHearts(addAmount);
soulHeartsRemaining -= addAmount;
break;
}
case isaac_typescript_definitions_1.HeartSubType.BLACK: {
player.AddBlackHearts(addAmount);
soulHeartsRemaining -= addAmount;
break;
}
case isaac_typescript_definitions_1.HeartSubType.BONE: {
player.AddBoneHearts(addAmount);
break;
}
}
}
/**
* Fill in the red heart containers.
*
* Rotten Hearts must be filled in first in order for this to work properly, since they conflict
* with half red hearts.
*
* We multiply by two because the `EntityPlayer.GetRottenHearts` function returns the actual
* number of rotten hearts, but the `EntityPlayer.AddRottenHearts` works like the other heart
* functions in that a value of 1 is equivalent to a half-heart.
*/
player.AddRottenHearts(playerHealth.rottenHearts * 2);
if (character === isaac_typescript_definitions_1.PlayerType.MAGDALENE_B) {
// Adding 1 heart to Tainted Magdalene will actually add two hearts.
(0, utils_1.repeat)(playerHealth.hearts, () => {
if (player.HasFullHearts()) {
return;
}
const hearts = player.GetHearts();
const maxHearts = player.GetMaxHearts();
if (hearts === maxHearts - 1) {
player.AddHearts(1);
return;
}
player.AddHearts(1);
player.AddHearts(-1);
});
}
else {
player.AddHearts(playerHealth.hearts);
}
player.AddGoldenHearts(playerHealth.goldenHearts);
player.AddBrokenHearts(playerHealth.brokenHearts);
// Set the Bethany / Tainted Bethany charges.
if (character === isaac_typescript_definitions_1.PlayerType.BETHANY) {
player.SetSoulCharge(playerHealth.soulCharges);
}
else if (character === isaac_typescript_definitions_1.PlayerType.BETHANY_B) {
player.SetBloodCharge(playerHealth.bloodCharges);
}
// Re-add the Alabaster Box, if present.
for (const { activeSlot, totalCharge } of alabasterBoxDescriptions) {
(0, playerCollectibles_1.setActiveItem)(player, isaac_typescript_definitions_1.CollectibleType.ALABASTER_BOX, activeSlot, totalCharge);
}
}
/**
* Helper function to see if a certain damage amount would deal "permanent" damage to Tainted
* Magdalene.
*
* Tainted Magdalene has "permanent" health and "temporary" health. When standing still and doing
* nothing, all of Tainted Magdalene's temporary health will eventually go away.
*
* Before using this function, it is expected that you check to see if the player is Tainted
* Magdalene first, or else it will give a nonsensical result.
*/
function wouldDamageTaintedMagdaleneNonTemporaryHeartContainers(player, damageAmount) {
// Regardless of the damage amount, damage to a player cannot remove a soul heart and a red heart
// at the same time.
const soulHearts = player.GetSoulHearts();
if (soulHearts > 0) {
return false;
}
// Regardless of the damage amount, damage to a player cannot remove a bone heart and a red heart
// at the same time.
const boneHearts = player.GetBoneHearts();
if (boneHearts > 0) {
return false;
}
// Account for rotten hearts eating away at more red hearts than usual.
const hearts = player.GetHearts();
const rottenHearts = player.GetRottenHearts();
const effectiveDamageAmount = damageAmount + Math.min(rottenHearts, damageAmount);
const heartsAfterDamage = hearts - effectiveDamageAmount;
const nonTemporaryMaxHearts = getTaintedMagdaleneNonTemporaryMaxHearts(player);
return heartsAfterDamage < nonTemporaryMaxHearts;
}