├── .gitignore ├── Ambush └── GetAmbushType.lua ├── Benchmark └── Benchmark.lua ├── BitSet128 └── CopyBitSet.lua ├── Bombs └── GetBombRadiusFromDamage.lua ├── Bosses ├── GetBosses.lua ├── IsSin.lua └── SpawnBoss.lua ├── Charge ├── AddCharge.lua ├── GetChargesAwayFromMax.lua ├── GetMaxCharges.lua ├── GetTotalCharge.lua └── IsActiveSlotDoubleCharged.lua ├── Collectibles ├── CollectibleHasFlag.lua ├── CollectibleSprite.lua ├── GetCollectibleDevilPrice.lua ├── GetCollectibleIndex.lua ├── GetCollectibleMaxCharges.lua ├── GetCollectibles.lua ├── IsCollectible.lua ├── SetCollectible.lua └── TryRemoveCollectible.lua ├── Color ├── CopyColor.lua ├── GetRandomColor.lua └── HexColor.lua ├── CustomCallbacks ├── AmbushCallbacks │ ├── AmbushFinishedCallback.lua │ ├── AmbushStartedCallback.lua │ └── AmbushWaveCallback.lua ├── BombCallbacks │ ├── BombExplodedCallback.lua │ └── BombInitLateCallback.lua ├── CollectibleCallbacks │ ├── CollectibleEmptyCallback.lua │ ├── CollectibleInitFirstCallback.lua │ └── GetCollectibleCustomItemPool.lua ├── EffectCallbacks │ ├── EffectInitLateCallback.lua │ └── EffectStateChangedCallback.lua ├── FamiliarCallbacks │ ├── FamiliarInitLateCallback.lua │ └── FamiliarStateChangedCallback.lua ├── FilterCallbacks │ ├── EntityDMGFilter.lua │ ├── NPCCollisionFilterCallback.lua │ ├── NPCDeathFilterCallback.lua │ ├── NPCInitFilterCallback.lua │ ├── NPCRenderFilterCallback.lua │ ├── NPCUpdateFilterCallback.lua │ ├── PostEntityRemoveFilterCallback.lua │ └── PreNPCUpdateCallbackFilter.lua ├── GreedModeCallbacks │ └── GreedModeWaveCallbacks.lua ├── GridEntityCallbacks │ ├── GridCollisionCallback.lua │ ├── GridEntityBrokenCallback.lua │ ├── GridEntityRemovedCallback.lua │ ├── GridEntityStateChanged.lua │ ├── GridEntityUpdateLogic.lua │ ├── GridInitCallback.lua │ ├── GridRenderCallback.lua │ └── GridUpdateCallback.lua ├── GridSpecificCallbacks │ ├── DoorRenderCallback.lua │ ├── DoorUpdateCallback.lua │ ├── PitRenderCallback.lua │ ├── PitUpdateCallback.lua │ ├── PoopRenderCallback.lua │ ├── PoopUpdateCallback.lua │ ├── PressurePlateRenderCallback.lua │ ├── PressurePlateUpdateCallback.lua │ ├── RockRenderCallback.lua │ ├── RockUpdateCallback.lua │ ├── SpikesRenderCallback.lua │ ├── SpikesUpdateCallback.lua │ ├── TNTRenderCallback.lua │ └── TNTUpdateCallback.lua ├── InternalCallbacks.lua ├── KnifeCallbacks │ └── KnifeInitLate.lua ├── LaserCallbacks │ └── LaserInitLateCallback.lua ├── NPCCallbacks │ ├── NPCInitLateCallback.lua │ └── NPCStateChangedCallback.lua ├── NewLevelCallbacks │ └── PreNewLevelCallback.lua ├── NewRoomCallbacks │ └── NewRoomEarlyCallback.lua ├── PickupCallbacks │ ├── PickupCollectCallback.lua │ ├── PickupInitFirstCallback.lua │ ├── PickupInitLateCallback.lua │ ├── PickupStateChangedCallback.lua │ └── PreGetPedestalCallback.lua ├── PlayerCallbacks │ ├── BerserkDeathCallback.lua │ ├── BoneSwingCallback.lua │ ├── EsauJrCallbacks │ │ ├── EsauJrCallbackLogic.lua │ │ ├── PostEsauJrCallback.lua │ │ └── PostFirstEsauJrCallback.lua │ ├── FatalDamageCallback.lua │ ├── FlipCallbacks │ │ ├── FlipCallbackLogic.lua │ │ ├── PostFirstFlipCallback.lua │ │ └── PostFlipCallback.lua │ ├── HolyMantleRemovedCallback.lua │ ├── InitCallbacks │ │ ├── PlayerInitFirst.lua │ │ └── PlayerInitLate.lua │ ├── InventoryCallbacks │ │ ├── ItemPickupCallbackLogic.lua │ │ ├── PlayerCollectibleAdded.lua │ │ ├── PlayerCollectibleRemoved.lua │ │ ├── PlayerGulpedTrinketAdded.lua │ │ ├── PlayerGulpedTrinketRemoved.lua │ │ ├── PostItemPickupCallback.lua │ │ └── PreItemPickupCallbacks.lua │ ├── ItemDischargeCallback.lua │ ├── PlayerHealthChangedCallback.lua │ ├── PostPlayerTypeChanged.lua │ ├── PurchaseCallback.lua │ ├── SacrificeCallback.lua │ ├── TransformationCallbackLogic.lua │ ├── TransformationGainedCallback.lua │ └── TransformationLostCallback.lua ├── ProjectileCallbacks │ └── ProjectileInitLateCallback.lua ├── RegisterCustomCallback.lua ├── ReorderedCallbacks │ ├── GameReorderedLogic.lua │ ├── GameStartedReorderedCallback.lua │ ├── GameStartedReorderedLastCallback.lua │ ├── NewLevelReorderedCallback.lua │ ├── NewRoomReorderedCallback.lua │ ├── PeffectUpdateReorderedCallback.lua │ ├── PlayerRenderReorderedCallback.lua │ ├── PlayerReorderedLogic.lua │ └── PlayerUpdateReorderedCallback.lua ├── ReviveCallbacks │ ├── CustomReviveLogic.lua │ ├── PostCustomRevive.lua │ └── PreCustomRevive.lua ├── RoomCallbacks │ └── RoomClearChangedCallback.lua ├── RoomSpecificCallback │ └── DiceRoomActivatedCallback.lua ├── SaveManagerCallbacks │ ├── PreLoadFromDisk.lua │ └── PreSaveToDisk.lua ├── SlotCallbacks │ ├── SlotAnimationChangedCallback.lua │ ├── SlotCollisionCallback.lua │ ├── SlotDestroyedCallback.lua │ ├── SlotInitCallback.lua │ ├── SlotPrizeCallback.lua │ ├── SlotRenderCallback.lua │ └── SlotUpdateCallback.lua └── TearCallbacks │ ├── TearInitLateCallback.lua │ └── TearInitVeryLateCallback.lua ├── CustomItemPools ├── AddCollectible.lua ├── GetCollectible.lua ├── IsInPool.lua ├── RegisterItemPool.lua └── RemoveCollectible.lua ├── Debug ├── GetTime.lua ├── GetTraceback.lua ├── IsDebugModeActive.lua ├── IsLuaDebugEnabled.lua └── Traceback.lua ├── Dimensions ├── GetDimension.lua └── InDimension.lua ├── Direction ├── AngleToDirection.lua ├── DirectionToDegrees.lua └── DirectionToVector.lua ├── Doors ├── CloseDoors.lua ├── DoorSlotFlag.lua ├── DoorSlotToDirection.lua ├── GetDoorEnterPosition.lua ├── GetDoorSlots.lua ├── GetDoors.lua ├── GetRoomShapeDoorSlotCoordinates.lua ├── GetSpecificDoors.lua ├── IsDoorSlotInRoomShape.lua ├── IsSpecificDoor.lua ├── OpenDoors.lua ├── RemoveDoors.lua └── UnusedDoorSlots.lua ├── Effects └── IsCloseEnoughToTriggerDiceFloor.lua ├── Entities ├── CanCollideWithGridEntity.lua ├── EntityData.lua ├── GetEntities.lua ├── GetSetEntityPositionsVelocities.lua ├── IsCollidingWithGrid.lua └── Spawn.lua ├── EntitySpecific ├── GetEntity.lua └── Spawn.lua ├── Enums ├── AmbushType.lua ├── BossID.lua ├── CallbackOptionalArgType.lua ├── CallbackReturnMode.lua ├── ConversionHeartSubType.lua ├── CustomCallback.lua ├── CustomReviveType.lua ├── DebugMode.lua ├── Dimension.lua ├── GridEntityStates.lua ├── GridEntityVariants.lua ├── GridEntityXMLType.lua ├── GridState.lua ├── HealthType.lua ├── InventoryType.lua ├── ItemConfigTag.lua ├── LineCheckMode.lua ├── PillEffectType.lua ├── ProjectilesMode.lua ├── Serialization.lua ├── ShockwaveSoundMode.lua ├── StageID.lua ├── SubTypes.lua ├── VariablePersistenceMode.lua └── Variants.lua ├── Familiars ├── CheckFamiliar.lua ├── GetPlayerFamiliars.lua └── SirenHelper.lua ├── GridEntities ├── ConvertXMLGridEntityType.lua ├── GetCollidingEntitiesWithGridEntity.lua ├── GetGridEntities.lua ├── GetSurroundingGridEntities.lua ├── GetTopLeftWall.lua ├── IsGridEntityBreakableByExplosion.lua ├── IsGridEntityBroken.lua ├── RemoveGridEntity.lua └── SpawnGridEntity.lua ├── GridIndexes ├── GetAllGridIndexes.lua └── GetGridIndexesBetween.lua ├── GridSpecific ├── GetGridEntity.lua └── SpawnGridEntity.lua ├── Input ├── GetActions.lua ├── IsActionPressed.lua ├── IsKeyboardPressed.lua ├── KeyboardToString.lua ├── MoveActions.lua └── ShootActions.lua ├── IsaacAPIClass ├── Comparisions.lua └── GetIsaacAPIClassName.lua ├── ItemPool └── IsCollectibleInItemPool.lua ├── Json └── json.lua ├── LICENSE.txt ├── Log ├── GetParentFunctionDescription.lua └── Log.lua ├── Pause └── Pause.lua ├── PickupSpecific ├── GetPickup.lua └── SpawnPickup.lua ├── Pickups ├── GetCoinValue.lua ├── IsChest.lua ├── PickupIndex.lua └── RedHearts.lua ├── Pills ├── GetPHDPillEffect.lua ├── GetPillEffectName.lua ├── GetPillEffectType.lua └── HorsePill.lua ├── Players ├── GetDeathAnimationName.lua ├── GetNumHitsRemaining.lua ├── GetPlayerFromEntity.lua ├── GetPlayers.lua ├── HealthContainers.lua ├── HealthConversion.lua ├── HealthTypes.lua ├── Inventory │ ├── AnyPlayerHasItem.lua │ ├── AnyPlayerHasTrinket.lua │ ├── PlayerHasCollectible.lua │ ├── PlayerInventory.lua │ └── PlayerInventoryLogic.lua ├── IsChildPlayer.lua ├── IsSpecificPlayer.lua ├── PlayerIndex.lua ├── RemoveCollectibleCostume.lua ├── TaintedLaz │ └── IsActiveTaintedLaz.lua ├── Trinkets │ ├── AddSmeltedTrinket.lua │ ├── SmeltedTrinketMultiplier.lua │ └── TemporaryRemoveTrinket.lua └── WillPlayerRevive.lua ├── README.md ├── RNG ├── CopyRNG.lua ├── GetRandomSeed.lua ├── NewRNG.lua └── SetSeed.lua ├── Random ├── GetRandom.lua ├── GetRandomFloat.lua ├── GetRandomInt.lua ├── RandomFromTable.lua └── RandomFromWeighted.lua ├── RoomSpecific └── SpecificRooms.lua ├── Rooms ├── EmptyRoom.lua ├── GetGridWidth.lua ├── InBossRoom.lua ├── IsGridIndexInRoom.lua ├── RoomData.lua ├── RoomHistory.lua └── UpdateRoom.lua ├── Run ├── IsGreedMode.lua └── RunUnlockAchievements.lua ├── SaveManager ├── AddPersistentVariable.lua ├── GetPersistentVariable.lua ├── GlowingHourglass.lua ├── LoadFromDisk.lua ├── RemovePersistentVariable.lua ├── ResetPersistentValue.lua ├── SaveDataManager.lua ├── SaveToDisk.lua ├── SetPersistentVariable.lua └── VariableResetter.lua ├── Serialize ├── BitSet128.lua ├── Color.lua ├── KColor.lua ├── RNG.lua ├── Serialize.lua ├── TableWithNumberKeys.lua └── Vector.lua ├── Shockwaves ├── CreateShockwaves.lua ├── IsCustomShockwave.lua ├── ShockwaveBehaviour.lua └── ShockwaveParams.lua ├── Sprites ├── GetLastFrame.lua └── SpriteEquals.lua ├── Stage ├── CalculateStageType.lua ├── CalculateStageTypeRepentance.lua ├── GetEffectiveStage.lua ├── OnAscent.lua ├── OnFirstFloor.lua └── OnRepentanceStage.lua ├── TSIL.lua ├── Trinkets └── GoldenTrinket.lua ├── UI ├── Hearts.lua └── ScreenPosition.lua ├── Utils ├── DeepCopy │ └── DeepCopy.lua ├── Easing │ └── Easings.lua ├── Flags │ ├── AddFlags.lua │ ├── CountBits.lua │ ├── HasFlags.lua │ └── RemoveFlags.lua ├── Functions │ ├── RunNextCallback.lua │ ├── RunNextLevel.lua │ ├── RunNextRoom.lua │ └── Scheduler.lua ├── Math │ ├── CircleIntersectingRectangle.lua │ ├── Clamp.lua │ ├── IsInRectangle.lua │ ├── Lerp.lua │ ├── Round.lua │ └── ScalarProduct.lua ├── String │ ├── EndsWith.lua │ ├── Split.lua │ ├── StartsWith.lua │ └── TrimPrefix.lua └── Tables │ ├── AllMatch.lua │ ├── ConstructDictionaryFromTable.lua │ ├── Copy.lua │ ├── CopyUserdataValuesToTable.lua │ ├── Count.lua │ ├── Equals.lua │ ├── Filter.lua │ ├── FindFirst.lua │ ├── ForEach.lua │ ├── GetDictionaryKeys.lua │ ├── GetNumbersFromTable.lua │ ├── HasNumberKeys.lua │ ├── IsArray.lua │ ├── IsEmpty.lua │ ├── IsInTable.lua │ ├── IterateTableInOrder.lua │ ├── Map.lua │ ├── Merge.lua │ ├── Remove.lua │ ├── SomeMatch.lua │ └── TableHasKeys.lua ├── Vector ├── CopyVector.lua ├── Equals.lua ├── FuzzyEq.lua ├── GetRandomVector.lua ├── HasLength.lua └── VectorToDirection.lua ├── dependencies.json ├── docs.lua └── scripts.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .luarc.json 3 | metadata.xml -------------------------------------------------------------------------------- /Ambush/GetAmbushType.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get the corresponding ambush type for the current room. Returns nil if 2 | ---the current room does not correspond to any particular ambush type 3 | ---@return AmbushType? 4 | function TSIL.Ambush.GetAmbushType() 5 | local room = Game():GetRoom() 6 | local roomType = room:GetType() 7 | 8 | if roomType == RoomType.ROOM_BOSSRUSH then 9 | return TSIL.Enums.AmbushType.BOSS_RUSH 10 | elseif roomType == RoomType.ROOM_CHALLENGE then 11 | return TSIL.Enums.AmbushType.CHALLENGE_ROOM 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /Benchmark/Benchmark.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to benchmark the performance of a function. 2 | --- This function is variadic, which means that you can supply as many functions as you want to 3 | --- benchmark. 4 | --- 5 | --- This function uses the `Isaac.GetTime` method to record how long the function took to execute. 6 | --- This method only reports time in milliseconds. For this reason, if you are benchmarking smaller 7 | --- functions, then you should provide a very high value for the number of trials. 8 | ---@param numTrials integer 9 | ---@vararg function 10 | ---@return number[] @ A table containing the average time in milliseconds for each function (this will be printed in the log) 11 | function TSIL.Benchmark.Benchmark(numTrials, ...) 12 | local functions = {...} 13 | 14 | TSIL.Log.Log("Benchmarking " .. #functions .. " function(s) with " .. numTrials .. " trials.") 15 | 16 | ---@type number[] 17 | local averages = {} 18 | 19 | for i, v in pairs(functions) do 20 | local totalTimeInMilliseconds = 0 21 | 22 | for i = 1, numTrials do 23 | local startTimeInMilliseconds = Isaac.GetTime() 24 | v() 25 | local endTimeInMilliseconds = Isaac.GetTime() 26 | local elapsedTimeMilliseconds = endTimeInMilliseconds - startTimeInMilliseconds 27 | totalTimeInMilliseconds = totalTimeInMilliseconds + elapsedTimeMilliseconds 28 | end 29 | 30 | local averageTimeMilliseconds = totalTimeInMilliseconds / numTrials 31 | TSIL.Log.Log("The average time of the function at index " .. i .. " is: " .. averageTimeMilliseconds .. " milliseconds") 32 | table.insert(averages, averageTimeMilliseconds) 33 | end 34 | 35 | return averages 36 | end -------------------------------------------------------------------------------- /BitSet128/CopyBitSet.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to copy a `BitSet128` Isaac API class. 2 | ---@param bitSet128 BitSet128 3 | ---@return BitSet128 4 | function TSIL.BitSet128.CopyBitSet128(bitSet128) 5 | if not TSIL.IsaacAPIClass.IsBitSet128(bitSet128) then 6 | error("Failed to copy a BitSet128 object since the provided object was not a userdata BitSet128 class.") 7 | end 8 | 9 | local lowBits = bitSet128.l 10 | local highBits = bitSet128.h 11 | return BitSet128(lowBits, highBits) 12 | end -------------------------------------------------------------------------------- /Bombs/GetBombRadiusFromDamage.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to find out how large a bomb explosion is based on the damage inflicted. 2 | ---@param damage number 3 | ---@return number 4 | function TSIL.Bombs.GetBombRadiusFromDamage(damage) 5 | if damage > 175 then 6 | return 105 7 | elseif damage <= 140 then 8 | return 75 9 | else 10 | return 90 11 | end 12 | end -------------------------------------------------------------------------------- /Bosses/GetBosses.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get all of the bosses in the room. 2 | ---@param entityType EntityType? @Default: -1 | If specified, will only get the bosses that match the type. Default is -1, which matches every type. 3 | ---@param variant integer? @Default: -1 | If specified, will only get the bosses that match the variant. Default is -1, which matches every variant. 4 | ---@param subType integer? @Default: -1 | If specified, will only get the bosses that match the sub-type. Default is -1, which matches every sub-type. 5 | ---@param ignoreFriendly boolean? @Default: false 6 | ---@return EntityNPC[] 7 | function TSIL.Bosses.GetBosses(entityType, variant, subType, ignoreFriendly) 8 | ---@diagnostic disable-next-line: cast-local-type 9 | entityType = entityType or -1 10 | variant = variant or -1 11 | subType = subType or -1 12 | if ignoreFriendly == nil then 13 | ignoreFriendly = false 14 | end 15 | 16 | local npcs = TSIL.EntitySpecific.GetNPCs(entityType, variant, subType, ignoreFriendly) 17 | local aliveNPCs = {} 18 | 19 | for _, v in pairs(npcs) do 20 | if v:IsBoss() then 21 | table.insert(aliveNPCs, v) 22 | end 23 | end 24 | 25 | return aliveNPCs 26 | end 27 | 28 | -------------------------------------------------------------------------------- /Bosses/IsSin.lua: -------------------------------------------------------------------------------- 1 | local SIN_LIST = { 2 | [EntityType.ENTITY_SLOTH] = true, 3 | [EntityType.ENTITY_LUST] = true, 4 | [EntityType.ENTITY_WRAITH] = true, 5 | [EntityType.ENTITY_GLUTTONY] = true, 6 | [EntityType.ENTITY_GREED] = true, 7 | [EntityType.ENTITY_ENVY] = true, 8 | [EntityType.ENTITY_PRIDE] = true 9 | } 10 | ---Helper function to check if the provided NPC is a Sin miniboss, such as Sloth or Lust. 11 | ---@param npc EntityNPC 12 | ---@return boolean 13 | function TSIL.Bosses.IsSin(npc) 14 | if SIN_LIST[npc.Type] ~= nil then 15 | return true 16 | end 17 | return false 18 | end -------------------------------------------------------------------------------- /Charge/GetChargesAwayFromMax.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the amount of charges away from the maximum charge that a particular 2 | --- player is. 3 | --- 4 | --- This function accounts for The Battery. For example, if the player has 2/6 charges on a D6, this 5 | --- function will return 10 (because there are 4 charges remaining on the base charge and 6 charges 6 | --- remaining on The Battery charge). 7 | ---@param player EntityPlayer @The player to get the charges from 8 | ---@param activeSlot ActiveSlot? @Default: `ActiveSlot.SLOT_PRIMARY` | The slot to get the charges from. 9 | ---@return integer 10 | function TSIL.Charge.GetChargesAwayFromMax(player, activeSlot) 11 | local totalCharge = TSIL.Charge.GetTotalCharge(player, activeSlot) 12 | local hasBattery = player:HasCollectible(CollectibleType.COLLECTIBLE_BATTERY) 13 | local maxCharges = TSIL.Charge.GetMaxCharges(player, activeSlot) 14 | 15 | ---@type number 16 | local effectiveMaxCharges 17 | 18 | if hasBattery then 19 | effectiveMaxCharges = maxCharges * 2 20 | else 21 | effectiveMaxCharges = maxCharges 22 | end 23 | 24 | return effectiveMaxCharges - totalCharge; 25 | end -------------------------------------------------------------------------------- /Charge/GetMaxCharges.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get the maximum number of charges the player has for a given active slot. 2 | --- 3 | ---Useful since just checking the item config won't account for items that can have multiple number of 4 | ---charges, like Blank Card. 5 | ---@param player EntityPlayer @The player to get the charges from 6 | ---@param activeSlot ActiveSlot? @Default: `ActiveSlot.SLOT_PRIMARY` | The slot to get the charges from. 7 | ---@return integer 8 | function TSIL.Charge.GetMaxCharges(player, activeSlot) 9 | if not activeSlot then activeSlot = ActiveSlot.SLOT_PRIMARY end 10 | 11 | local previousCharge = TSIL.Charge.GetTotalCharge(player, activeSlot) 12 | --We set the charge to an arbitrary high value to ensure it always maxes out 13 | player:SetActiveCharge(math.floor(math.maxinteger/100), activeSlot) 14 | local currentCharge = player:GetActiveCharge(activeSlot) 15 | player:SetActiveCharge(previousCharge, activeSlot) 16 | 17 | return currentCharge 18 | end 19 | -------------------------------------------------------------------------------- /Charge/GetTotalCharge.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the combined normal charge and the battery charge for the player's active 2 | --- item. This is useful because you have to add these two values together when setting the active 3 | --- charge. 4 | ---@param player EntityPlayer @The player to get the charges from. 5 | ---@param activeSlot ActiveSlot? @Default: `ActiveSlot.SLOT_PRIMARY` | The slot to get the charges from. 6 | ---@return integer 7 | function TSIL.Charge.GetTotalCharge(player, activeSlot) 8 | activeSlot = activeSlot or ActiveSlot.SLOT_PRIMARY 9 | 10 | local activeCharge = player:GetActiveCharge(activeSlot) 11 | local batteryCharge = player:GetBatteryCharge(activeSlot) 12 | 13 | return activeCharge + batteryCharge 14 | end -------------------------------------------------------------------------------- /Charge/IsActiveSlotDoubleCharged.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if a player's active item is "double charged", meaning that it has both 2 | --- a full normal charge and a full charge from The Battery. 3 | ---@param player EntityPlayer @The player to check 4 | ---@param activeSlot ActiveSlot? @Default: `ActiveSlot.SLOT_PRIMARY` | The slot to check. 5 | ---@return boolean 6 | function TSIL.Charge.IsActiveSlotDoubleCharged(player, activeSlot) 7 | activeSlot = activeSlot or ActiveSlot.SLOT_PRIMARY 8 | local collectibleType = player:GetActiveItem(activeSlot) 9 | local batteryCharge = player:GetBatteryCharge(activeSlot) 10 | local maxCharges = TSIL.Collectibles.GetCollectibleMaxCharges(collectibleType) 11 | 12 | return batteryCharge >= maxCharges 13 | end -------------------------------------------------------------------------------- /Collectibles/CollectibleHasFlag.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a collectible type has a given flag 2 | ---@param collectibleType CollectibleType 3 | ---@param flag ItemConfigTag 4 | ---@return boolean 5 | function TSIL.Collectibles.CollectibleHasFlag(collectibleType, flag) 6 | local itemConfig = Isaac.GetItemConfig() 7 | local itemConfigItem = itemConfig:GetCollectible(collectibleType) 8 | 9 | return TSIL.Utils.Flags.HasFlags(itemConfigItem.Tags, flag) 10 | end -------------------------------------------------------------------------------- /Collectibles/CollectibleSprite.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if two collectible sprites are the same. 2 | ---@param sprite1 Sprite 3 | ---@param sprite2 Sprite 4 | ---@return boolean 5 | function TSIL.Collectibles.CollectibleSpriteEquals(sprite1, sprite2) 6 | local xStart = -1 7 | local xFinish = 1 8 | local xIncrement = 1 9 | local yStart = -40 10 | local yFinish = 10 11 | local yIncrement = 3 12 | 13 | return TSIL.Sprites.SpriteEquals( 14 | sprite1, 15 | sprite2, 16 | 1, 17 | xStart, 18 | xFinish, 19 | xIncrement, 20 | yStart, 21 | yIncrement, 22 | yFinish 23 | ) 24 | end 25 | 26 | 27 | --- Helper function to change the sprite of a collectible pedestal 28 | ---@param collectible EntityPickup 29 | ---@param spriteSheet string? @Optional. If not set, the sprite will be removed, like if the item had already been taken. 30 | function TSIL.Collectibles.SetCollectibleSprite(collectible, spriteSheet) 31 | local sprite = collectible:GetSprite() 32 | 33 | if not spriteSheet then 34 | sprite:ReplaceSpritesheet(1, "") 35 | sprite:ReplaceSpritesheet(3, "") 36 | else 37 | sprite:ReplaceSpritesheet(1, spriteSheet) 38 | end 39 | sprite:LoadGraphics() 40 | end -------------------------------------------------------------------------------- /Collectibles/GetCollectibleMaxCharges.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the maximum amount of charges that a collectible has. Returns 0 if the 2 | --- provided collectible type was not valid. 3 | ---@param collectibleType CollectibleType 4 | ---@return number 5 | function TSIL.Collectibles.GetCollectibleMaxCharges(collectibleType) 6 | local itemConfigItem = Isaac.GetItemConfig():GetCollectible(collectibleType) 7 | 8 | if not itemConfigItem then 9 | return 0 10 | else 11 | return itemConfigItem.MaxCharges 12 | end 13 | end -------------------------------------------------------------------------------- /Collectibles/TryRemoveCollectible.lua: -------------------------------------------------------------------------------- 1 | --- Empties an item pedestal as if a player had already 2 | --- picked it up. 3 | --- 4 | --- If it's a shop item, it removes it completely. 5 | ---@param collectible EntityPickup 6 | ---@return boolean 7 | function TSIL.Collectibles.TryRemoveCollectible(collectible) 8 | if collectible.Variant ~= PickupVariant.PICKUP_COLLECTIBLE or 9 | collectible.SubType == CollectibleType.COLLECTIBLE_NULL then 10 | return false 11 | end 12 | 13 | local sprite = collectible:GetSprite() 14 | collectible.SubType = CollectibleType.COLLECTIBLE_NULL 15 | 16 | if collectible.Price == 0 then 17 | sprite:SetFrame("Empty",0) 18 | sprite:ReplaceSpritesheet(4, "gfx/none.png") 19 | sprite:LoadGraphics() 20 | return true 21 | else 22 | collectible:Remove() 23 | return true 24 | end 25 | end -------------------------------------------------------------------------------- /Color/CopyColor.lua: -------------------------------------------------------------------------------- 1 | ---Copies a color. 2 | ---@param color Color 3 | ---@return Color 4 | function TSIL.Color.CopyColor(color) 5 | if not TSIL.IsaacAPIClass.IsColor(color) then 6 | error("Failed to copy a Color object since the provided object was not a userdata Color class.") 7 | end 8 | 9 | return Color( 10 | color.R, 11 | color.G, 12 | color.B, 13 | color.A, 14 | color.RO, 15 | color.GO, 16 | color.BO 17 | ) 18 | end 19 | 20 | 21 | ---Helper function to copy a `KColor` Isaac API class. 22 | ---@param kColor KColor 23 | ---@return KColor 24 | function TSIL.Color.CopyKColor(kColor) 25 | if not TSIL.IsaacAPIClass.IsKColor(kColor) then 26 | error("Failed to copy a KColor object since the provided object was not a userdata KColor class.") 27 | end 28 | 29 | return KColor( 30 | kColor.Red, 31 | kColor.Green, 32 | kColor.Blue, 33 | kColor.Alpha 34 | ) 35 | end -------------------------------------------------------------------------------- /Color/GetRandomColor.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get a random color. 2 | --- 3 | ---Only randomizes the R, G and B fields. 4 | ---@param seedOrRNG? integer | RNG @Default: `TSIL.RNG.GetRandomSeed()` | The `Seed` or `RNG` object to use. If an `RNG` object is provided, the `RNG:Next` method will be called. 5 | ---@param alpha number? @Default: 1 | The alpha value to use. 6 | ---@return Color 7 | function TSIL.Color.GetRandomColor(seedOrRNG, alpha) 8 | seedOrRNG = seedOrRNG or TSIL.RNG.GetRandomSeed() 9 | 10 | ---@type RNG 11 | local rng 12 | 13 | if TSIL.IsaacAPIClass.IsRNG(seedOrRNG) then 14 | ---@cast seedOrRNG RNG 15 | rng = seedOrRNG 16 | else 17 | ---@cast seedOrRNG integer? 18 | rng = TSIL.RNG.NewRNG(seedOrRNG) 19 | end 20 | 21 | local r = TSIL.Random.GetRandom(rng) 22 | local g = TSIL.Random.GetRandom(rng) 23 | local b = TSIL.Random.GetRandom(rng) 24 | 25 | return Color(r, g, b, alpha) 26 | end 27 | -------------------------------------------------------------------------------- /Color/HexColor.lua: -------------------------------------------------------------------------------- 1 | local HEX_STRING_LENGTH = 6 2 | 3 | ---@param hex string 4 | ---@return integer 5 | ---@return integer 6 | ---@return integer 7 | local function HexToRGB(hex) 8 | hex = hex:gsub("%#", "") 9 | 10 | if hex:len() ~= HEX_STRING_LENGTH then 11 | return 0, 0, 0 12 | end 13 | 14 | local rString = hex:sub(1, 2) 15 | local r = tonumber("0x" .. rString) 16 | if r == nil then 17 | return 0, 0, 0 18 | end 19 | 20 | local gString = hex:sub(3, 4) 21 | local g = tonumber("0x" .. gString) 22 | if g == nil then 23 | return 0, 0, 0 24 | end 25 | 26 | local bString = hex:sub(5, 6) 27 | local b = tonumber("0x" .. bString) 28 | if b == nil then 29 | return 0, 0, 0 30 | end 31 | 32 | return r, g, b 33 | end 34 | 35 | 36 | ---Converts a hex string like "#33aa33" to a Color object. 37 | ---@param hex string 38 | ---@param alpha number 39 | ---@return Color 40 | function TSIL.Color.HexToColor(hex, alpha) 41 | local r, g, b = HexToRGB(hex) 42 | return Color(r/255, g/255, b/255, alpha) 43 | end 44 | 45 | 46 | ---Converts a hex string like "#33aa33" to a KColor object. 47 | ---@param hex string 48 | ---@param alpha number 49 | ---@return KColor 50 | function TSIL.Color.HexToKColor(hex, alpha) 51 | local r, g, b = HexToRGB(hex) 52 | return KColor(r/255, g/255, b/255, alpha) 53 | end -------------------------------------------------------------------------------- /CustomCallbacks/AmbushCallbacks/AmbushFinishedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_AMBUSH_FINISHED 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_AMBUSH_FINISHED, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GENERIC 6 | ) 7 | 8 | local function OnTSILLoad() 9 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "ambushDone_AMBUSH_FINISHED_CALLBACK", false, TSIL.Enums.VariablePersistenceMode.RESET_ROOM) 10 | end 11 | TSIL.__AddInternalCallback( 12 | "AMBUSH_FINISHED_CALLBACK_ON_TSIL_LOAD", 13 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 14 | OnTSILLoad 15 | ) 16 | 17 | 18 | local function OnUpdate() 19 | local wasAmbushDone = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "ambushDone_AMBUSH_FINISHED_CALLBACK") 20 | 21 | if wasAmbushDone then return end 22 | 23 | local room = Game():GetRoom() 24 | local ambushDone = room:IsAmbushDone() 25 | if not ambushDone then 26 | return 27 | end 28 | 29 | TSIL.SaveManager.SetPersistentVariable(TSIL.__MOD, "ambushDone_AMBUSH_FINISHED_CALLBACK", true) 30 | 31 | local ambushType = TSIL.Ambush.GetAmbushType(); 32 | if ambushType ~= nil then 33 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_AMBUSH_FINISHED, ambushType) 34 | end 35 | end 36 | TSIL.__AddInternalCallback( 37 | "AMBUSH_FINISHED_CALLBACK_ON_UPDATE", 38 | ModCallbacks.MC_POST_UPDATE, 39 | OnUpdate 40 | ) -------------------------------------------------------------------------------- /CustomCallbacks/AmbushCallbacks/AmbushStartedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_AMBUSH_STARTED 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_AMBUSH_STARTED, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GENERIC 6 | ) 7 | 8 | local function OnTSILLoad() 9 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "ambushActive_AMBUSH_STARTED_CALLBACK", false, TSIL.Enums.VariablePersistenceMode.RESET_ROOM) 10 | end 11 | TSIL.__AddInternalCallback( 12 | "AMBUSH_STARTED_CALLBACK_ON_TSIL_LOAD", 13 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 14 | OnTSILLoad 15 | ) 16 | 17 | 18 | local function OnUpdate() 19 | local wasAmbushActive = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "ambushActive_AMBUSH_STARTED_CALLBACK") 20 | 21 | if wasAmbushActive then return end 22 | 23 | local room = Game():GetRoom() 24 | local ambushActive = room:IsAmbushActive() 25 | if not ambushActive then 26 | return 27 | end 28 | 29 | TSIL.SaveManager.SetPersistentVariable(TSIL.__MOD, "ambushActive_AMBUSH_STARTED_CALLBACK", true) 30 | 31 | local ambushType = TSIL.Ambush.GetAmbushType() 32 | if ambushType ~= nil then 33 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_AMBUSH_STARTED, ambushType) 34 | end 35 | end 36 | TSIL.__AddInternalCallback( 37 | "AMBUSH_STARTED_CALLBACK_ON_UPDATE", 38 | ModCallbacks.MC_POST_UPDATE, 39 | OnUpdate 40 | ) -------------------------------------------------------------------------------- /CustomCallbacks/BombCallbacks/BombExplodedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_BOMB_EXPLODED 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_BOMB_EXPLODED, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param bomb EntityBomb 10 | local function OnBombUpdate(_, bomb) 11 | if bomb:GetSprite():IsPlaying("Explode") then 12 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_BOMB_EXPLODED, bomb) 13 | end 14 | end 15 | TSIL.__AddInternalCallback( 16 | "BOMB_EXPLODED_CALLBACK_POST_BOMB_UPDATE", 17 | ModCallbacks.MC_POST_BOMB_UPDATE, 18 | OnBombUpdate 19 | ) -------------------------------------------------------------------------------- /CustomCallbacks/BombCallbacks/BombInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_BOMB_INIT_LATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_BOMB_INIT_LATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param bomb EntityBomb 10 | local function OnBombUpdate(_, bomb) 11 | if bomb.FrameCount == 1 then 12 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_BOMB_INIT_LATE, bomb) 13 | end 14 | end 15 | TSIL.__AddInternalCallback( 16 | "BOMB_INIT_CALLBACK_POST_BOMB_UPDATE", 17 | ModCallbacks.MC_POST_BOMB_UPDATE, 18 | OnBombUpdate 19 | ) -------------------------------------------------------------------------------- /CustomCallbacks/CollectibleCallbacks/CollectibleEmptyCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_COLLECTIBLE_EMPTY 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_COLLECTIBLE_EMPTY, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GENERIC 7 | ) 8 | 9 | 10 | local function OnTSILLoad() 11 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "collectibleTypeMap_COLLETIBLE_EMPTY_CALLBACK", {}, TSIL.Enums.VariablePersistenceMode.RESET_ROOM) 12 | end 13 | TSIL.__AddInternalCallback( 14 | "COLLECTIBLE_EMPTY_CALLBACK_TSIL_LOADED", 15 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 16 | OnTSILLoad 17 | ) 18 | 19 | 20 | ---@param collectible EntityPickup 21 | local function OnCollectibleUpdate(_, collectible) 22 | local collectibleTypeMap = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "collectibleTypeMap_COLLETIBLE_EMPTY_CALLBACK") 23 | local ptrHash = GetPtrHash(collectible) 24 | 25 | local oldCollectibleType = collectibleTypeMap[tostring(ptrHash)] 26 | 27 | if oldCollectibleType == nil then 28 | oldCollectibleType = collectible.SubType 29 | end 30 | 31 | collectibleTypeMap[tostring(ptrHash)] = collectible.SubType 32 | 33 | if oldCollectibleType ~= collectible.SubType and 34 | collectible.SubType == CollectibleType.COLLECTIBLE_NULL then 35 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_COLLECTIBLE_EMPTY, collectible, oldCollectibleType) 36 | end 37 | end 38 | TSIL.__AddInternalCallback( 39 | "COLLECTIBLE_EMPTY_CALLBACK_PICKUP_COLLECTIBLE_UPDATE", 40 | ModCallbacks.MC_POST_PICKUP_UPDATE, 41 | OnCollectibleUpdate, 42 | CallbackPriority.DEFAULT, 43 | PickupVariant.PICKUP_COLLECTIBLE 44 | ) -------------------------------------------------------------------------------- /CustomCallbacks/CollectibleCallbacks/CollectibleInitFirstCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_COLLECTIBLE_INIT_FIRST 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_COLLECTIBLE_INIT_FIRST, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnTSILLoad() 10 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "seenCollectibles_COLLETIBLE_INIT_FIRST_CALLBACK", {}, TSIL.Enums.VariablePersistenceMode.RESET_RUN) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "COLLECTIBLE_INIT_FIRST_CALLBACK_TSIL_LOADED", 14 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 15 | OnTSILLoad 16 | ) 17 | 18 | 19 | ---@param collectible EntityPickup 20 | local function OnCollectibleInit(_, collectible) 21 | local seenCollectibles = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "seenCollectibles_COLLETIBLE_INIT_FIRST_CALLBACK") 22 | local collectibleIndex = TSIL.Collectibles.GetCollectibleIndex(collectible) 23 | 24 | if not TSIL.Utils.Tables.IsIn(seenCollectibles, collectibleIndex) then 25 | seenCollectibles[#seenCollectibles+1] = collectibleIndex 26 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_COLLECTIBLE_INIT_FIRST, collectible) 27 | end 28 | end 29 | TSIL.__AddInternalCallback( 30 | "COLLECTIBLE_INIT_FIRST_CALLBACK_PICKUP_COLLECTIBLE_INIT", 31 | ModCallbacks.MC_POST_PICKUP_INIT, 32 | OnCollectibleInit, 33 | CallbackPriority.DEFAULT, 34 | PickupVariant.PICKUP_COLLECTIBLE 35 | ) -------------------------------------------------------------------------------- /CustomCallbacks/CollectibleCallbacks/GetCollectibleCustomItemPool.lua: -------------------------------------------------------------------------------- 1 | --##POST_GET_COLLECTIBLE_CUSTOM_ITEM_POOL 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GET_COLLECTIBLE_CUSTOM_ITEM_POOL, 5 | TSIL.Enums.CallbackReturnMode.NEXT_ARGUMENT, 6 | TSIL.Enums.CallbackOptionalArgType.GENERIC, 7 | TSIL.Enums.CallbackOptionalArgType.GENERIC 8 | ) -------------------------------------------------------------------------------- /CustomCallbacks/EffectCallbacks/EffectInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_EFFECT_INIT_LATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_EFFECT_INIT_LATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param effect EntityEffect 10 | local function OnEffectUpdate(_, effect) 11 | if effect.FrameCount == 0 then 12 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_EFFECT_INIT_LATE, effect) 13 | end 14 | end 15 | TSIL.__AddInternalCallback( 16 | "EFFECT_INIT_LATE_CALLBACK_ON_EFFECT_UPDATE", 17 | ModCallbacks.MC_POST_EFFECT_UPDATE, 18 | OnEffectUpdate 19 | ) -------------------------------------------------------------------------------- /CustomCallbacks/EffectCallbacks/EffectStateChangedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_EFFECT_STATE_CHANGED 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_EFFECT_STATE_CHANGED, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnTSILLoad() 10 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "effectStatesLastFrame_EFFECT_STATE_CHANGE_CALLBACK", {}, TSIL.Enums.VariablePersistenceMode.RESET_ROOM) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "EFFECT_STATE_CHANGED_CALLBACK_ON_TSIL_LOAD", 14 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 15 | OnTSILLoad 16 | ) 17 | 18 | 19 | ---@param effect EntityEffect 20 | local function OnEffectUpdate(_, effect) 21 | local effectPtr = GetPtrHash(effect) 22 | local effectPtrStr = tostring(effectPtr) 23 | 24 | local effectStatesLastFrame = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "effectStatesLastFrame_EFFECT_STATE_CHANGE_CALLBACK") 25 | 26 | local effectStateLastFrame = effectStatesLastFrame[effectPtrStr] 27 | local effectStateCurrentFrame = effect.State 28 | 29 | if effectStateLastFrame ~= nil and 30 | effectStateLastFrame ~= effectStateCurrentFrame then 31 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_EFFECT_STATE_CHANGED, effect, effectStateLastFrame, effectStateCurrentFrame) 32 | end 33 | 34 | effectStatesLastFrame[effectPtrStr] = effectStateCurrentFrame 35 | end 36 | TSIL.__AddInternalCallback( 37 | "EFFECT_STATE_CHANGED_CALLBACK_ON_EFFECT_UPDATE", 38 | ModCallbacks.MC_POST_EFFECT_UPDATE, 39 | OnEffectUpdate 40 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FamiliarCallbacks/FamiliarInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_FAMILIAR_INIT_LATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_FAMILIAR_INIT_LATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param familiar EntityFamiliar 10 | local function OnFamiliarUpdate(_, familiar) 11 | local hasTriggered = TSIL.Entities.GetEntityData( 12 | TSIL.__MOD, 13 | familiar, 14 | "HasTriggeredCallback_FAMILIAR_INIT_LATE_CALLBACK" 15 | ) 16 | 17 | if not hasTriggered then 18 | TSIL.Entities.SetEntityData( 19 | TSIL.__MOD, 20 | familiar, 21 | "HasTriggeredCallback_FAMILIAR_INIT_LATE_CALLBACK", 22 | true 23 | ) 24 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_FAMILIAR_INIT_LATE, familiar) 25 | end 26 | end 27 | TSIL.__AddInternalCallback( 28 | "FAMILIAR_INIT_LATE_CALLBACK_ON_FAMILIAR_UPDATE", 29 | ModCallbacks.MC_FAMILIAR_UPDATE, 30 | OnFamiliarUpdate 31 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FamiliarCallbacks/FamiliarStateChangedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_FAMILIAR_STATE_CHANGED 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_FAMILIAR_STATE_CHANGED, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnTSILLoad() 10 | TSIL.SaveManager.AddPersistentVariable( 11 | TSIL.__MOD, 12 | "familiarStates_FAMILIAR_STATE_CHANGED_CALLBACK", 13 | {}, 14 | TSIL.Enums.VariablePersistenceMode.RESET_ROOM 15 | ) 16 | end 17 | TSIL.__AddInternalCallback( 18 | "FAMILIAR_STATE_CHANGED_CALLBACK_ON_TSIL_LOAD", 19 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 20 | OnTSILLoad 21 | ) 22 | 23 | 24 | ---@param familiar EntityFamiliar 25 | local function OnFamiliarUpdate(_, familiar) 26 | local familiarStates = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "familiarStates_FAMILIAR_STATE_CHANGED_CALLBACK") 27 | local familiarPtr = GetPtrHash(familiar) 28 | 29 | local previousState = familiarStates[tostring(familiarPtr)] 30 | local currentState = familiar.State 31 | familiarStates[tostring(familiarPtr)] = currentState 32 | 33 | if previousState == nil or previousState == currentState then 34 | return 35 | end 36 | 37 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_FAMILIAR_STATE_CHANGED, familiar, previousState, currentState) 38 | end 39 | TSIL.__AddInternalCallback( 40 | "FAMILIAR_STATE_CHANGED_CALLBACK_ON_FAMILIAR_UPDATE", 41 | ModCallbacks.MC_FAMILIAR_UPDATE, 42 | OnFamiliarUpdate 43 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/EntityDMGFilter.lua: -------------------------------------------------------------------------------- 1 | --##ENTITY_TAKE_DMG_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.ENTITY_TAKE_DMG_FILTER, 4 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnEntityDamage(_, entity, amount, flags, source, countdown) 10 | return TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.ENTITY_TAKE_DMG_FILTER, entity, amount, flags, source, countdown) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "ENTITY_DMG_FILTER_CALLBACK_ENTITY_DMG", 14 | ModCallbacks.MC_ENTITY_TAKE_DMG, 15 | OnEntityDamage 16 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/NPCCollisionFilterCallback.lua: -------------------------------------------------------------------------------- 1 | --##PRE_NPC_COLLISION_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.PRE_NPC_COLLISION_FILTER, 4 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnNPCCollision(_, entity, collider, isLow) 10 | return TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.PRE_NPC_COLLISION_FILTER, entity, collider, isLow) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "NPC_COLLISION_FILTER_CALLBACK_NPC_COLLISION", 14 | ModCallbacks.MC_PRE_NPC_COLLISION, 15 | OnNPCCollision 16 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/NPCDeathFilterCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_NPC_DEATH_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_NPC_DEATH_FILTER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnNPCDeath(_, entity) 10 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_NPC_DEATH_FILTER, entity) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "NPC_DEATH_FILTER_CALLBACK_NPC_DEATH", 14 | ModCallbacks.MC_POST_NPC_DEATH, 15 | OnNPCDeath 16 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/NPCInitFilterCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_NPC_INIT_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_NPC_INIT_FILTER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnNPCInit(_, entity) 10 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_NPC_INIT_FILTER, entity) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "NPC_INIT_FILTER_CALLBACK_NPC_INIT", 14 | ModCallbacks.MC_POST_NPC_INIT, 15 | OnNPCInit 16 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/NPCRenderFilterCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_NPC_RENDER_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_NPC_RENDER_FILTER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnNPCRender(_, entity) 10 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_NPC_RENDER_FILTER, entity) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "NPC_RENDER_FILTER_CALLBACK_NPC_RENDER", 14 | ModCallbacks.MC_POST_NPC_RENDER, 15 | OnNPCRender 16 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/NPCUpdateFilterCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_NPC_UPDATE_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_NPC_UPDATE_FILTER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnNPCUpdate(_, entity) 10 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_NPC_UPDATE_FILTER, entity) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "NPC_UPDATE_FILTER_CALLBACK_NPC_UPDATE", 14 | ModCallbacks.MC_NPC_UPDATE, 15 | OnNPCUpdate 16 | ) -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/PostEntityRemoveFilterCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_ENTITY_REMOVE_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_ENTITY_REMOVE_FILTER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function onEntityRemove(_, entity) 10 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_ENTITY_REMOVE_FILTER, entity) 11 | end 12 | 13 | TSIL.__AddInternalCallback( 14 | "ENTITY_REMOVE_FILTER_CALLBACK_ENTITY_REMOVE", 15 | ModCallbacks.MC_POST_ENTITY_REMOVE, 16 | onEntityRemove 17 | ) 18 | -------------------------------------------------------------------------------- /CustomCallbacks/FilterCallbacks/PreNPCUpdateCallbackFilter.lua: -------------------------------------------------------------------------------- 1 | --##PRE_NPC_UPDATE_FILTER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.PRE_NPC_UPDATE_FILTER, 4 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function PreNPCUpdate(_, entity) 10 | local returned = TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.PRE_NPC_UPDATE_FILTER, entity) 11 | return returned 12 | end 13 | TSIL.__AddInternalCallback( 14 | "PRE_NPC_UPDATE_FILTER_CALLBACK_PRE_NPC_UPDATE", 15 | ModCallbacks.MC_PRE_NPC_UPDATE, 16 | PreNPCUpdate 17 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GreedModeCallbacks/GreedModeWaveCallbacks.lua: -------------------------------------------------------------------------------- 1 | --##POST_GREED_MODE_WAVE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_GREED_MODE_WAVE 4 | ) 5 | 6 | 7 | local function OnTSILLoad() 8 | TSIL.SaveManager.AddPersistentVariable( 9 | TSIL.__MOD, 10 | "greedWave_GREED_MODE_WAVE_CALLBACK", 11 | 0, 12 | TSIL.Enums.VariablePersistenceMode.RESET_RUN 13 | ) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "GREED_MODE_WAVE_CALLBACK_ON_TSIL_LOAD", 17 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 18 | OnTSILLoad 19 | ) 20 | 21 | 22 | local function OnUpdate() 23 | if not TSIL.Run.IsGreedMode() then 24 | return 25 | end 26 | 27 | local level = Game():GetLevel() 28 | 29 | local previousGreedWave = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "greedWave_GREED_MODE_WAVE_CALLBACK") 30 | local currentGreedWave = level.GreedModeWave 31 | TSIL.SaveManager.SetPersistentVariable(TSIL.__MOD, "greedWave_GREED_MODE_WAVE_CALLBACK", currentGreedWave) 32 | 33 | if currentGreedWave > previousGreedWave then 34 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_GREED_MODE_WAVE, previousGreedWave, currentGreedWave) 35 | end 36 | end 37 | TSIL.__AddInternalCallback( 38 | "GREED_MODE_WAVE_CALLBACK_ON_UPDATE", 39 | ModCallbacks.MC_POST_UPDATE, 40 | OnUpdate 41 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridEntityCallbacks/GridCollisionCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_GRID_COLLISION 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GRID_COLLISION, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT, 7 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 8 | ) 9 | 10 | 11 | ---@param grid GridEntity 12 | local function OnGridUpdate(_, grid) 13 | local closeEntities = Isaac.FindInRadius(grid.Position, 120) 14 | 15 | local filteredEntities = TSIL.Utils.Tables.Filter(closeEntities, function (_, entity) 16 | return TSIL.Entities.CanCollideWithGridEntity(entity, grid) and 17 | TSIL.Utils.Math.IsCircleIntersectingWithRectangle(grid.Position, Vector(40, 40), entity.Position, entity.Size) 18 | end) 19 | 20 | for _, entity in ipairs(filteredEntities) do 21 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_GRID_COLLISION, grid, entity) 22 | end 23 | end 24 | TSIL.__AddInternalCallback( 25 | "GRID_COLLISION_CALLBACK_POST_GRID_UPDATE", 26 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 27 | OnGridUpdate 28 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridEntityCallbacks/GridEntityBrokenCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_GRID_ENTITY_BROKEN 2 | --##use CustomCallbacks/GridEntityCallbacks/GridEntityUpdateLogic.lua 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_BROKEN, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT 7 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridEntityCallbacks/GridEntityRemovedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_GRID_ENTITY_REMOVED 2 | --##use CustomCallbacks/GridEntityCallbacks/GridEntityUpdateLogic.lua 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_REMOVED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GENERIC, 7 | TSIL.Enums.CallbackOptionalArgType.GENERIC 8 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridEntityCallbacks/GridEntityStateChanged.lua: -------------------------------------------------------------------------------- 1 | --##POST_GRID_ENTITY_STATE_CHANGED 2 | --##use CustomCallbacks/GridEntityCallbacks/GridEntityUpdateLogic.lua 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_STATE_CHANGED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT 7 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridEntityCallbacks/GridInitCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_GRID_ENTITY_INIT 2 | --##use CustomCallbacks/GridEntityCallbacks/GridEntityUpdateLogic.lua 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_INIT, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT 7 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridEntityCallbacks/GridRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_GRID_ENTITY_RENDER 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT 7 | ) 8 | 9 | 10 | local function OnRender() 11 | local gridEntities = TSIL.GridEntities.GetGridEntities() 12 | 13 | for _, gridEntity in ipairs(gridEntities) do 14 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, gridEntity) 15 | end 16 | end 17 | TSIL.__AddInternalCallback( 18 | "GRID_RENDER_CALLBACK_POST_RENDER", 19 | ModCallbacks.MC_POST_RENDER, 20 | OnRender 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridEntityCallbacks/GridUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_GRID_ENTITY_UPDATE 2 | --##use CustomCallbacks/GridEntityCallbacks/GridEntityUpdateLogic.lua 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT 7 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/DoorRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_DOOR_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_DOOR_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnDoorRender(_, gridEntity) 11 | local door = gridEntity:ToDoor() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_DOOR_RENDER, door) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "DOOR_RENDER_CALLBACK_GRID_ENTITY_RENDER", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, 18 | OnDoorRender, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_DOOR 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/DoorUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_DOOR_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_DOOR_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnDoorUpdate(_, gridEntity) 11 | local door = gridEntity:ToDoor() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_DOOR_UPDATE, door) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "DOOR_UPDATE_CALLBACK_GRID_ENTITY_UPDATE", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 18 | OnDoorUpdate, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_DOOR 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/PitRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PIT_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PIT_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnPitRender(_, gridEntity) 11 | local pit = gridEntity:ToPit() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PIT_RENDER, pit) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "PIT_RENDER_CALLBACK_GRID_ENTITY_RENDER", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, 18 | OnPitRender, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_PIT 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/PitUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PIT_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PIT_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnPitUpdate(_, gridEntity) 11 | local pit = gridEntity:ToPit() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PIT_UPDATE, pit) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "PIT_UPDATE_CALLBACK_GRID_ENTITY_UPDATE", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 18 | OnPitUpdate, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_PIT 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/PoopRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_POOP_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_POOP_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnPoopRender(_, gridEntity) 11 | local poop = gridEntity:ToPoop() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_POOP_RENDER, poop) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "POOP_RENDER_CALLBACK_GRID_ENTITY_RENDER", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, 18 | OnPoopRender, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_POOP 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/PoopUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_POOP_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_POOP_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnPoopUpdate(_, gridEntity) 11 | local poop = gridEntity:ToPoop() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_POOP_UPDATE, poop) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "POOP_UPDATE_CALLBACK_GRID_ENTITY_UPDATE", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 18 | OnPoopUpdate, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_POOP 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/PressurePlateRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PRESSURE_PLATE_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PRESSURE_PLATE_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnPressurePlateRender(_, gridEntity) 11 | local pressurePlate = gridEntity:ToPressurePlate() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PRESSURE_PLATE_RENDER, pressurePlate) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "PRESSURE_PLATE_RENDER_CALLBACK_GRID_ENTITY_RENDER", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, 18 | OnPressurePlateRender, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_PRESSURE_PLATE 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/PressurePlateUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PRESSURE_PLATE_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PRESSURE_PLATE_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnPressurePlateUpdate(_, gridEntity) 11 | local pressurePlate = gridEntity:ToPressurePlate() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PRESSURE_PLATE_UPDATE, pressurePlate) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "PRESSURE_PLATE_UPDATE_CALLBACK_GRID_ENTITY_UPDATE", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 18 | OnPressurePlateUpdate, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_PRESSURE_PLATE 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/RockRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_ROCK_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_ROCK_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnRockRender(_, gridEntity) 11 | local rock = gridEntity:ToRock() 12 | 13 | if rock ~= nil then 14 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_ROCK_RENDER, rock) 15 | end 16 | end 17 | TSIL.__AddInternalCallback( 18 | "ROCK_RENDER_CALLBACK_GRID_ENTITY_RENDER", 19 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, 20 | OnRockRender, 21 | CallbackPriority.DEFAULT 22 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/RockUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_ROCK_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_ROCK_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_TYPE_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnRockUpdate(_, gridEntity) 11 | local rock = gridEntity:ToRock() 12 | 13 | if rock ~= nil then 14 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_ROCK_UPDATE, rock) 15 | end 16 | end 17 | TSIL.__AddInternalCallback( 18 | "ROCK_UPDATE_CALLBACK_GRID_ENTITY_UPDATE", 19 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 20 | OnRockUpdate, 21 | CallbackPriority.DEFAULT 22 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/SpikesRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SPIKES_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_SPIKES_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnSpikesRender(_, gridEntity) 11 | local spikes = gridEntity:ToSpikes() 12 | 13 | if spikes ~= nil then 14 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SPIKES_RENDER, spikes) 15 | end 16 | end 17 | TSIL.__AddInternalCallback( 18 | "SPIKES_RENDER_CALLBACK_GRID_ENTITY_RENDER", 19 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 20 | OnSpikesRender, 21 | CallbackPriority.DEFAULT 22 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/SpikesUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SPIKES_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_SPIKES_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnSpikesUpdate(_, gridEntity) 11 | local spikes = gridEntity:ToSpikes() 12 | 13 | if spikes ~= nil then 14 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SPIKES_UPDATE, spikes) 15 | end 16 | end 17 | TSIL.__AddInternalCallback( 18 | "SPIKES_UPDATE_CALLBACK_GRID_ENTITY_UPDATE", 19 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 20 | OnSpikesUpdate, 21 | CallbackPriority.DEFAULT 22 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/TNTRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_TNT_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_TNT_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnTNTRender(_, gridEntity) 11 | local TNT = gridEntity:ToTNT() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_TNT_RENDER, TNT) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "TNT_RENDER_CALLBACK_GRID_ENTITY_RENDER", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_RENDER, 18 | OnTNTRender, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_TNT 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/GridSpecificCallbacks/TNTUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_TNT_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_TNT_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.GRID_VARIANT 6 | ) 7 | 8 | 9 | ---@param gridEntity GridEntity 10 | local function OnTNTUpdate(_, gridEntity) 11 | local TNT = gridEntity:ToTNT() 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_TNT_UPDATE, TNT) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "TNT_UPDATE_CALLBACK_GRID_ENTITY_UPDATE", 17 | TSIL.Enums.CustomCallback.POST_GRID_ENTITY_UPDATE, 18 | OnTNTUpdate, 19 | CallbackPriority.DEFAULT, 20 | GridEntityType.GRID_TNT 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/KnifeCallbacks/KnifeInitLate.lua: -------------------------------------------------------------------------------- 1 | --##POST_KNIFE_INIT_LATE 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_KNIFE_INIT_LATE, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 7 | ) 8 | 9 | ---@param knife EntityKnife 10 | local function OnKnifeUpdate(_, knife) 11 | local hasTriggered = TSIL.Entities.GetEntityData( 12 | TSIL.__MOD, 13 | knife, 14 | "HasTriggeredCallback_KNIFE_INIT_LATE_CALLBACK" 15 | ) 16 | 17 | if not hasTriggered then 18 | TSIL.Entities.SetEntityData( 19 | TSIL.__MOD, 20 | knife, 21 | "HasTriggeredCallback_KNIFE_INIT_LATE_CALLBACK", 22 | true 23 | ) 24 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_KNIFE_INIT_LATE, knife) 25 | end 26 | end 27 | TSIL.__AddInternalCallback( 28 | "KNIFE_INIT_LATE_CALLBACK_ON_KNIFE_UPDATE", 29 | ModCallbacks.MC_POST_KNIFE_UPDATE, 30 | OnKnifeUpdate 31 | ) -------------------------------------------------------------------------------- /CustomCallbacks/LaserCallbacks/LaserInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_LASER_INIT_LATE 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_LASER_INIT_LATE, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 7 | ) 8 | 9 | 10 | ---@param laser EntityLaser 11 | local function OnLaserUpdate(_, laser) 12 | local hasTriggered = TSIL.Entities.GetEntityData( 13 | TSIL.__MOD, 14 | laser, 15 | "HasTriggeredCallback_LASER_INIT_LATE_CALLBACK" 16 | ) 17 | 18 | if not hasTriggered then 19 | TSIL.Entities.SetEntityData( 20 | TSIL.__MOD, 21 | laser, 22 | "HasTriggeredCallback_LASER_INIT_LATE_CALLBACK", 23 | true 24 | ) 25 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_LASER_INIT_LATE, laser) 26 | end 27 | end 28 | TSIL.__AddInternalCallback( 29 | "LASER_INIT_LATE_CALLBACK_ON_LASER_UPDATE", 30 | ModCallbacks.MC_POST_LASER_UPDATE, 31 | OnLaserUpdate 32 | ) -------------------------------------------------------------------------------- /CustomCallbacks/NPCCallbacks/NPCInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_NPC_INIT_LATE 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_NPC_INIT_LATE, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 7 | ) 8 | 9 | 10 | local function OnTSILLoad() 11 | TSIL.SaveManager.AddPersistentVariable( 12 | TSIL.__MOD, 13 | "npcsInRoom_KINFE_INIT_LATE_CALLBACK", 14 | {}, 15 | TSIL.Enums.VariablePersistenceMode.RESET_ROOM 16 | ) 17 | end 18 | TSIL.__AddInternalCallback( 19 | "NPC_INIT_LATE_CALLBACK_ON_TSIL_LOAD", 20 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 21 | OnTSILLoad 22 | ) 23 | 24 | 25 | ---@param npc EntityNPC 26 | local function OnNpcUpdate(_, npc) 27 | local npcsInRoom = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "npcsInRoom_KINFE_INIT_LATE_CALLBACK") 28 | local npcPtr = GetPtrHash(npc) 29 | 30 | if not TSIL.Utils.Tables.IsIn(npcsInRoom, npcPtr) then 31 | npcsInRoom[#npcsInRoom+1] = npcPtr 32 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_NPC_INIT_LATE, npc) 33 | end 34 | end 35 | TSIL.__AddInternalCallback( 36 | "NPC_INIT_LATE_CALLBACK_ON_NPC_UPDATE", 37 | ModCallbacks.MC_NPC_UPDATE, 38 | OnNpcUpdate 39 | ) -------------------------------------------------------------------------------- /CustomCallbacks/NPCCallbacks/NPCStateChangedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_NPC_STATE_CHANGED 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_NPC_STATE_CHANGED, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnTSILLoad() 10 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "npcStatesLastFrame_NPC_STATE_CHANGE_CALLBACK", {}, TSIL.Enums.VariablePersistenceMode.RESET_ROOM) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "NPC_STATE_CHANGED_CALLBACK_ON_TSIL_LOAD", 14 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 15 | OnTSILLoad 16 | ) 17 | 18 | 19 | ---@param npc EntityNPC 20 | local function OnNPCUpdate(_, npc) 21 | local npcPtr = GetPtrHash(npc) 22 | local npcPtrStr = tostring(npcPtr) 23 | 24 | local npcStatesLastFrame = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "npcStatesLastFrame_NPC_STATE_CHANGE_CALLBACK") 25 | 26 | local npcStateLastFrame = npcStatesLastFrame[npcPtrStr] 27 | local npcStateCurrentFrame = npc.State 28 | 29 | if npcStateLastFrame ~= nil and 30 | npcStateLastFrame ~= npcStateCurrentFrame then 31 | TSIL.__TriggerCustomCallback( 32 | TSIL.Enums.CustomCallback.POST_NPC_STATE_CHANGED, 33 | npc, 34 | npcStateLastFrame, 35 | npcStateCurrentFrame 36 | ) 37 | end 38 | 39 | npcStatesLastFrame[npcPtrStr] = npcStateCurrentFrame 40 | end 41 | TSIL.__AddInternalCallback( 42 | "NPC_STATE_CHANGED_CALLBACK_ON_NPC_UPDATE", 43 | ModCallbacks.MC_NPC_UPDATE, 44 | OnNPCUpdate 45 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PickupCallbacks/PickupCollectCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PICKUP_COLLECT 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PICKUP_COLLECT, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnTSILLoad() 10 | TSIL.SaveManager.AddPersistentVariable( 11 | TSIL.__MOD, 12 | "CollectedPickups_PICKUP_COLLECT_CALLBACK", 13 | {}, 14 | TSIL.Enums.VariablePersistenceMode.RESET_ROOM 15 | ) 16 | end 17 | TSIL.__AddInternalCallback( 18 | "PICKUP_COLLECT_CALLBACK_ON_TSIL_LOAD", 19 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 20 | OnTSILLoad 21 | ) 22 | 23 | 24 | ---@param pickup EntityPickup 25 | local function OnPickupRender(_, pickup) 26 | local pickupSpr = pickup:GetSprite() 27 | 28 | if pickupSpr:GetAnimation() ~= "Collect" then return end 29 | 30 | local pickupPtr = GetPtrHash(pickup) 31 | 32 | local collectedPickups = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "CollectedPickups_PICKUP_COLLECT_CALLBACK") 33 | 34 | if TSIL.Utils.Tables.IsIn(collectedPickups, pickupPtr) then return end 35 | 36 | TSIL.__TriggerCustomCallback( 37 | TSIL.Enums.CustomCallback.POST_PICKUP_COLLECT, 38 | pickup 39 | ) 40 | collectedPickups[#collectedPickups+1] = pickupPtr 41 | end 42 | TSIL.__AddInternalCallback( 43 | "PICKUP_COLLECT_CALLBACK_CALLBACK_ON_PICKUP_RENDER", 44 | ModCallbacks.MC_POST_PICKUP_RENDER, 45 | OnPickupRender 46 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PickupCallbacks/PickupInitFirstCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PICKUP_INIT_FIRST 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_PICKUP_INIT_FIRST, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 7 | ) 8 | 9 | 10 | ---@param pickup EntityPickup 11 | local function OnPickupInit(_, pickup) 12 | local room = Game():GetRoom() 13 | local roomDesc = TSIL.Rooms.GetRoomDescriptor() 14 | 15 | local roomFrameCount = room:GetFrameCount() 16 | local visitedCount = roomDesc.VisitedCount 17 | 18 | if roomFrameCount > 0 or visitedCount == 0 then 19 | TSIL.__TriggerCustomCallback( 20 | TSIL.Enums.CustomCallback.POST_PICKUP_INIT_FIRST, 21 | pickup 22 | ) 23 | end 24 | end 25 | TSIL.__AddInternalCallback( 26 | "PICKUP_INIT_FIRST_CALLBACK_ON_PICKUP_INIT", 27 | ModCallbacks.MC_POST_PICKUP_INIT, 28 | OnPickupInit 29 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PickupCallbacks/PickupInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PICKUP_INIT_LATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PICKUP_INIT_LATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param pickup EntityPickup 10 | local function OnPickupUpdate(_, pickup) 11 | local hasTriggered = TSIL.Entities.GetEntityData( 12 | TSIL.__MOD, 13 | pickup, 14 | "HasTriggeredCallback_PICKUP_INIT_LATE_CALLBACK" 15 | ) 16 | 17 | if not hasTriggered then 18 | TSIL.Entities.SetEntityData( 19 | TSIL.__MOD, 20 | pickup, 21 | "HasTriggeredCallback_LASER_INIT_LATE_CALLBACK", 22 | true 23 | ) 24 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PICKUP_INIT_LATE, pickup) 25 | end 26 | end 27 | TSIL.__AddInternalCallback( 28 | "PICKUP_INIT_LATE_CALLBACK_PICKUP_UPDATE", 29 | ModCallbacks.MC_POST_PICKUP_UPDATE, 30 | OnPickupUpdate 31 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PickupCallbacks/PickupStateChangedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PICKUP_STATE_CHANGED 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PICKUP_STATE_CHANGED, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnTSILLoad() 10 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "pickupStatesLastFrame_PICKUP_STATE_CHANGE_CALLBACK", {}, TSIL.Enums.VariablePersistenceMode.RESET_ROOM) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "PICKUP_STATE_CHANGED_CALLBACK_ON_TSIL_LOAD", 14 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 15 | OnTSILLoad 16 | ) 17 | 18 | 19 | ---@param pickup EntityPickup 20 | local function OnPickupUpdate(_, pickup) 21 | local pickupPtr = GetPtrHash(pickup) 22 | local pickupPtrStr = tostring(pickupPtr) 23 | 24 | local pickupStatesLastFrame = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "pickupStatesLastFrame_PICKUP_STATE_CHANGE_CALLBACK") 25 | 26 | local pickupStateLastFrame = pickupStatesLastFrame[pickupPtrStr] 27 | local pickupStateCurrentFrame = pickup.State 28 | 29 | if pickupStateLastFrame ~= nil and 30 | pickupStateLastFrame ~= pickupStateCurrentFrame then 31 | TSIL.__TriggerCustomCallback( 32 | TSIL.Enums.CustomCallback.POST_PICKUP_STATE_CHANGED, 33 | pickup, 34 | pickupStateLastFrame, 35 | pickupStateCurrentFrame 36 | ) 37 | end 38 | 39 | pickupStatesLastFrame[pickupPtrStr] = pickupStateCurrentFrame 40 | end 41 | TSIL.__AddInternalCallback( 42 | "PICKUP_STATE_CHANGED_CALLBACK_ON_PICKUP_UPDATE", 43 | ModCallbacks.MC_POST_PICKUP_UPDATE, 44 | OnPickupUpdate 45 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PickupCallbacks/PreGetPedestalCallback.lua: -------------------------------------------------------------------------------- 1 | --##PRE_GET_PEDESTAL 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.PRE_GET_PEDESTAL, 5 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 7 | TSIL.Enums.CallbackOptionalArgType.ENTITY_SUBTYPE 8 | ) 9 | 10 | 11 | ---@param collectible EntityPickup 12 | ---@param collider Entity 13 | local function PreCollectibleCollision(_, collectible, collider) 14 | if collectible.SubType == CollectibleType.COLLECTIBLE_NULL then return end 15 | 16 | local player = collider:ToPlayer() 17 | if player == nil then return end 18 | 19 | local numCoins = player:GetNumCoins(); 20 | if collectible.Price > numCoins then return end 21 | 22 | if collectible.Wait > 0 or player.ItemHoldCooldown > 0 then return end 23 | 24 | if player:IsHoldingItem() then return end 25 | 26 | return TSIL.__TriggerCustomCallback( 27 | TSIL.Enums.CustomCallback.PRE_GET_PEDESTAL, 28 | player, 29 | collectible 30 | ) 31 | end 32 | TSIL.__AddInternalCallback( 33 | "PRE_GET_PEDESTAL_CALLBACK_ON_PICKUP_COLLISION", 34 | ModCallbacks.MC_PRE_PICKUP_COLLISION, 35 | PreCollectibleCollision, 36 | CallbackPriority.DEFAULT, 37 | PickupVariant.PICKUP_COLLECTIBLE 38 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/BerserkDeathCallback.lua: -------------------------------------------------------------------------------- 1 | --##PRE_BERSERK_DEATH 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.PRE_BERSERK_DEATH, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 7 | ) 8 | 9 | 10 | ---@param player EntityPlayer 11 | local function OnPeffectUpdate(_, player) 12 | if TSIL.Players.IsChildPlayer(player) then 13 | return 14 | end 15 | 16 | local effects = player:GetEffects(); 17 | local berserkEffect = effects:GetCollectibleEffect(CollectibleType.COLLECTIBLE_BERSERK); 18 | local numHitsRemaining = TSIL.Players.GetPlayerNumHitsRemaining(player) 19 | 20 | if berserkEffect ~= nil and 21 | berserkEffect.Cooldown == 1 and 22 | numHitsRemaining == 0 and 23 | not TSIL.Players.WillPlayerRevive(player) then 24 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.PRE_BERSERK_DEATH, player) 25 | end 26 | end 27 | TSIL.__AddInternalCallback( 28 | "PRE_BERSERK_DEATH_CALLBACK_ON_PEFFECT_UPDATE_REORDERED", 29 | TSIL.Enums.CustomCallback.POST_PEFFECT_UPDATE_REORDERED, 30 | OnPeffectUpdate 31 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/EsauJrCallbacks/PostEsauJrCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_ESAU_JR 2 | --##use CustomCallbacks/PlayerCallbacks/EsauJrCallbacks/EsauJrCallbackLogic.lua 3 | 4 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_ESAU_JR) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/EsauJrCallbacks/PostFirstEsauJrCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_FIRST_ESAU_JR 2 | --##use CustomCallbacks/PlayerCallbacks/EsauJrCallbacks/EsauJrCallbackLogic.lua 3 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_FIRST_ESAU_JR) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/FlipCallbacks/PostFirstFlipCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_FIRST_FLIP 2 | --##use CustomCallbacks/PlayerCallbacks/FlipCallbacks/FillCallbackLogic.lua 3 | 4 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_FIRST_FLIP) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/FlipCallbacks/PostFlipCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_FLIP 2 | --##use CustomCallbacks/PlayerCallbacks/FlipCallbacks/FillCallbackLogic.lua 3 | 4 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_FLIP) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InitCallbacks/PlayerInitFirst.lua: -------------------------------------------------------------------------------- 1 | --##POST_PLAYER_INIT_FIRST 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_PLAYER_INIT_FIRST, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 7 | ) 8 | 9 | 10 | local function OnNewRoomReordered() 11 | if not TSIL.RoomSpecific.InGenesisRoom() then 12 | return 13 | end 14 | 15 | for _, player in ipairs(TSIL.Players.GetPlayers()) do 16 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PLAYER_INIT_FIRST, player) 17 | end 18 | end 19 | TSIL.__AddInternalCallback( 20 | "PLAYER_INIT_FIRST_CALLBACK_ON_NEW_ROOM_REORDERED", 21 | TSIL.Enums.CustomCallback.POST_NEW_ROOM_REORDERED, 22 | OnNewRoomReordered 23 | ) 24 | 25 | 26 | ---@param player EntityPlayer 27 | local function OnPlayerInitLate(_, player) 28 | if TSIL.Players.IsChildPlayer(player) then 29 | return 30 | end 31 | 32 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PLAYER_INIT_FIRST, player) 33 | end 34 | TSIL.__AddInternalCallback( 35 | "PLAYER_INIT_FIRST_CALLBACK_ON_PLAYER_INIT_LATE", 36 | TSIL.Enums.CustomCallback.POST_PLAYER_INIT_LATE, 37 | OnPlayerInitLate 38 | ) 39 | -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InitCallbacks/PlayerInitLate.lua: -------------------------------------------------------------------------------- 1 | --##POST_PLAYER_INIT_LATE 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_PLAYER_INIT_LATE, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 7 | ) 8 | 9 | 10 | local function OnTSILLoad() 11 | TSIL.SaveManager.AddPersistentVariable( 12 | TSIL.__MOD, 13 | "PlayersTriggered_PLAYER_INIT_LATE_CALLBACK", 14 | {}, 15 | TSIL.Enums.VariablePersistenceMode.RESET_RUN 16 | ) 17 | end 18 | TSIL.__AddInternalCallback( 19 | "PLAYER_INIT_LATE_CALLBACK_ON_TSIL_LOAD", 20 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 21 | OnTSILLoad 22 | ) 23 | 24 | 25 | ---@param player EntityPlayer 26 | local function OnPeffectUpdate(_, player) 27 | local playerIndex = TSIL.Players.GetPlayerIndex(player) 28 | 29 | local playersTriggered = TSIL.SaveManager.GetPersistentVariable( 30 | TSIL.__MOD, 31 | "PlayersTriggered_PLAYER_INIT_LATE_CALLBACK" 32 | ) 33 | 34 | if not TSIL.Utils.Tables.IsIn(playersTriggered, playerIndex) then 35 | playersTriggered[#playersTriggered+1] = playerIndex 36 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PLAYER_INIT_LATE, player) 37 | end 38 | end 39 | TSIL.__AddInternalCallback( 40 | "PLAYER_INIT_LATE_CALLBACK_ON_PEFFECT_UPDATE_REORDERED", 41 | TSIL.Enums.CustomCallback.POST_PEFFECT_UPDATE_REORDERED, 42 | OnPeffectUpdate 43 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InventoryCallbacks/PlayerCollectibleAdded.lua: -------------------------------------------------------------------------------- 1 | --##use Players/Inventory/PlayerInventoryLogic.lua 2 | --##POST_PLAYER_COLLECTIBLE_ADDED 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.POST_PLAYER_COLLECTIBLE_ADDED, 6 | TSIL.Enums.CallbackReturnMode.NONE, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 8 | TSIL.Enums.CallbackOptionalArgType.GENERIC, 9 | TSIL.Enums.CallbackOptionalArgType.GENERIC 10 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InventoryCallbacks/PlayerCollectibleRemoved.lua: -------------------------------------------------------------------------------- 1 | --##use Players/Inventory/PlayerInventoryLogic.lua 2 | --##POST_PLAYER_COLLECTIBLE_REMOVED 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.POST_PLAYER_COLLECTIBLE_REMOVED, 6 | TSIL.Enums.CallbackReturnMode.NONE, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 8 | TSIL.Enums.CallbackOptionalArgType.GENERIC 9 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InventoryCallbacks/PlayerGulpedTrinketAdded.lua: -------------------------------------------------------------------------------- 1 | --##use Players/Inventory/PlayerInventoryLogic.lua 2 | --##POST_PLAYER_GULPED_TRINKET_ADDED 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.POST_PLAYER_GULPED_TRINKET_ADDED, 6 | TSIL.Enums.CallbackReturnMode.NONE, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 8 | TSIL.Enums.CallbackOptionalArgType.GENERIC 9 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InventoryCallbacks/PlayerGulpedTrinketRemoved.lua: -------------------------------------------------------------------------------- 1 | --##use Players/Inventory/PlayerInventoryLogic.lua 2 | --##POST_PLAYER_GULPED_TRINKET_REMOVED 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.POST_PLAYER_GULPED_TRINKET_REMOVED, 6 | TSIL.Enums.CallbackReturnMode.NONE, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 8 | TSIL.Enums.CallbackOptionalArgType.GENERIC 9 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InventoryCallbacks/PostItemPickupCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/PlayerCallbacks/InventoryCallbacks/ItemPickupCallbackLogic.lua 2 | --##POST_ITEM_PICKUP 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.POST_ITEM_PICKUP, 6 | TSIL.Enums.CallbackReturnMode.NONE, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 8 | TSIL.Enums.CallbackOptionalArgType.GENERIC, 9 | TSIL.Enums.CallbackOptionalArgType.GENERIC 10 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/InventoryCallbacks/PreItemPickupCallbacks.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/PlayerCallbacks/InventoryCallbacks/ItemPickupCallbackLogic.lua 2 | --##PRE_ITEM_PICKUP 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.PRE_ITEM_PICKUP, 6 | TSIL.Enums.CallbackReturnMode.NONE, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 8 | TSIL.Enums.CallbackOptionalArgType.GENERIC, 9 | TSIL.Enums.CallbackOptionalArgType.GENERIC 10 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/SacrificeCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SACRIFICE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_SACRIFICE, 4 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 5 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 6 | ) 7 | 8 | 9 | local function OnTSILLoad() 10 | TSIL.SaveManager.AddPersistentVariable(TSIL.__MOD, "numSacrifices_POST_SACRIFICE_CALLBACK", 0, TSIL.Enums.VariablePersistenceMode.RESET_LEVEL) 11 | end 12 | TSIL.__AddInternalCallback( 13 | "POST_SACRIFICE_CALLBACK_ON_TSIL_LOAD", 14 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 15 | OnTSILLoad 16 | ) 17 | 18 | 19 | ---@param entity Entity 20 | ---@param damageFlags integer 21 | local function OnPlayerDamage(_, entity, _, damageFlags) 22 | local player = entity:ToPlayer() 23 | 24 | local room = Game():GetRoom() 25 | local roomType = room:GetType() 26 | 27 | local isSpikeDamage = TSIL.Utils.Flags.HasFlags(damageFlags, DamageFlag.DAMAGE_SPIKES) 28 | 29 | if roomType == RoomType.ROOM_SACRIFICE and isSpikeDamage then 30 | local numSacrifices = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "numSacrifices_POST_SACRIFICE_CALLBACK") 31 | TSIL.SaveManager.SetPersistentVariable(TSIL.__MOD, "numSacrifices_POST_SACRIFICE_CALLBACK", numSacrifices + 1) 32 | 33 | local skipDamage = TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SACRIFICE, player, numSacrifices) 34 | return skipDamage 35 | end 36 | end 37 | TSIL.__AddInternalCallback( 38 | "POST_SACRIFICE_CALLBACK_ON_ENTITY_DAMAGE_PLAYER", 39 | ModCallbacks.MC_ENTITY_TAKE_DMG, 40 | OnPlayerDamage, 41 | CallbackPriority.DEFAULT, 42 | EntityType.ENTITY_PLAYER 43 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/TransformationGainedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_TRANSFORMATION_GAINED 2 | --##use CustomCallbacks/PlayerCallbacks/TransformationCallbackLogic.lua 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_TRANSFORMATION_GAINED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 7 | TSIL.Enums.CallbackOptionalArgType.GENERIC 8 | ) -------------------------------------------------------------------------------- /CustomCallbacks/PlayerCallbacks/TransformationLostCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_TRANSFORMATION_LOST 2 | --##use CustomCallbacks/PlayerCallbacks/TransformationCallbackLogic.lua 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_TRANSFORMATION_LOST, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT, 7 | TSIL.Enums.CallbackOptionalArgType.GENERIC 8 | ) -------------------------------------------------------------------------------- /CustomCallbacks/ProjectileCallbacks/ProjectileInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_PROJECTILE_INIT_LATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_PROJECTILE_INIT_LATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param projectile EntityProjectile 10 | local function OnProjectileUpdate(_, projectile) 11 | if projectile.FrameCount == 0 then 12 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_PROJECTILE_INIT_LATE, projectile) 13 | end 14 | end 15 | TSIL.__AddInternalCallback( 16 | "PROJECTILE_INIT_LATE_CALLBACK_ON_PROJECTILE_UPDATE", 17 | ModCallbacks.MC_POST_PROJECTILE_UPDATE, 18 | OnProjectileUpdate 19 | ) -------------------------------------------------------------------------------- /CustomCallbacks/ReorderedCallbacks/GameStartedReorderedCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReorderedCallbacks/GameReorderedLogic.lua 2 | --##POST_GAME_STARTED_REORDERED 3 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_GAME_STARTED_REORDERED) -------------------------------------------------------------------------------- /CustomCallbacks/ReorderedCallbacks/GameStartedReorderedLastCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReorderedCallbacks/GameReorderedLogic.lua 2 | --##POST_GAME_STARTED_REORDERED_LAST 3 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_GAME_STARTED_REORDERED_LAST) -------------------------------------------------------------------------------- /CustomCallbacks/ReorderedCallbacks/NewLevelReorderedCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReorderedCallbacks/GameReorderedLogic.lua 2 | --##POST_NEW_LEVEL_REORDERED 3 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_NEW_LEVEL_REORDERED) -------------------------------------------------------------------------------- /CustomCallbacks/ReorderedCallbacks/NewRoomReorderedCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReorderedCallbacks/GameReorderedLogic.lua 2 | --##POST_NEW_ROOM_REORDERED 3 | TSIL.__RegisterCustomCallback(TSIL.Enums.CustomCallback.POST_NEW_ROOM_REORDERED) -------------------------------------------------------------------------------- /CustomCallbacks/ReorderedCallbacks/PeffectUpdateReorderedCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReorderedCallbacks/PlayerReorderedLogic.lua 2 | --##POST_PEFFECT_UPDATE_REORDERED 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_PEFFECT_UPDATE_REORDERED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 7 | ) -------------------------------------------------------------------------------- /CustomCallbacks/ReorderedCallbacks/PlayerRenderReorderedCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReorderedCallbacks/PlayerReorderedLogic.lua 2 | --##POST_PLAYER_RENDER_REORDERED 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_PLAYER_RENDER_REORDERED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 7 | ) -------------------------------------------------------------------------------- /CustomCallbacks/ReorderedCallbacks/PlayerUpdateReorderedCallback.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReorderedCallbacks/PlayerReorderedLogic.lua 2 | --##POST_PLAYER_UPDATE_REORDERED 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_PLAYER_UPDATE_REORDERED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 7 | ) -------------------------------------------------------------------------------- /CustomCallbacks/ReviveCallbacks/PostCustomRevive.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReviveCallbacks/CustomReviveLogic.lua 2 | --##POST_CUSTOM_REVIVE 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.POST_CUSTOM_REVIVE, 6 | TSIL.Enums.CallbackReturnMode.NONE, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 8 | ) -------------------------------------------------------------------------------- /CustomCallbacks/ReviveCallbacks/PreCustomRevive.lua: -------------------------------------------------------------------------------- 1 | --##use CustomCallbacks/ReviveCallbacks/CustomReviveLogic.lua 2 | --##PRE_CUSTOM_REVIVE 3 | 4 | TSIL.__RegisterCustomCallback( 5 | TSIL.Enums.CustomCallback.PRE_CUSTOM_REVIVE, 6 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 7 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 8 | ) -------------------------------------------------------------------------------- /CustomCallbacks/RoomCallbacks/RoomClearChangedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_ROOM_CLEAR_CHANGED 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_ROOM_CLEAR_CHANGED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.GENERIC 7 | ) 8 | 9 | 10 | local function OnTSILLoad() 11 | TSIL.SaveManager.AddPersistentVariable( 12 | TSIL.__MOD, 13 | "WasRoomCleared_POST_ROOM_CLEAR_CHANGED_CALLBACK", 14 | nil, 15 | TSIL.Enums.VariablePersistenceMode.RESET_ROOM 16 | ) 17 | end 18 | TSIL.__AddInternalCallback( 19 | "POST_ROOM_CLEAR_CHANGED_CALLBACK_ON_TSIL_LOAD", 20 | TSIL.Enums.CustomCallback.POST_TSIL_LOAD, 21 | OnTSILLoad 22 | ) 23 | 24 | 25 | local function OnUpdate() 26 | local wasRoomCleared = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "WasRoomCleared_POST_ROOM_CLEAR_CHANGED_CALLBACK") 27 | local isRoomClear = Game():GetRoom():IsClear() 28 | TSIL.SaveManager.SetPersistentVariable( 29 | TSIL.__MOD, 30 | "WasRoomCleared_POST_ROOM_CLEAR_CHANGED_CALLBACK", 31 | isRoomClear, 32 | true 33 | ) 34 | 35 | if isRoomClear ~= wasRoomCleared and wasRoomCleared ~= nil then 36 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_ROOM_CLEAR_CHANGED, isRoomClear) 37 | end 38 | end 39 | TSIL.__AddInternalCallback( 40 | "POST_ROOM_CLEAR_CHANGED_CALLBACK_ON_UPDATE", 41 | ModCallbacks.MC_POST_UPDATE, 42 | OnUpdate 43 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SaveManagerCallbacks/PreLoadFromDisk.lua: -------------------------------------------------------------------------------- 1 | --##PRE_SAVE_MANAGER_LOAD_FROM_DISK 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.PRE_SAVE_MANAGER_LOAD_FROM_DISK, 4 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 5 | TSIL.Enums.CallbackOptionalArgType.GENERIC 6 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SaveManagerCallbacks/PreSaveToDisk.lua: -------------------------------------------------------------------------------- 1 | --##PRE_SAVE_MANAGER_SAVE_TO_DISK 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.PRE_SAVE_MANAGER_SAVE_TO_DISK, 4 | TSIL.Enums.CallbackReturnMode.SKIP_NEXT, 5 | TSIL.Enums.CallbackOptionalArgType.GENERIC 6 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SlotCallbacks/SlotAnimationChangedCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SLOT_ANIMATION_CHANGED 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_SLOT_ANIMATION_CHANGED, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 7 | ) 8 | 9 | ---@type table 10 | local slotAnimations = {} 11 | 12 | 13 | local function OnNewRoom() 14 | slotAnimations = {} 15 | end 16 | TSIL.__AddInternalCallback( 17 | "SLOT_ANIMATION_CHANGED_CALLBACK_NEW_ROOM", 18 | ModCallbacks.MC_POST_NEW_ROOM, 19 | OnNewRoom 20 | ) 21 | 22 | 23 | ---@param slot Entity 24 | local function OnSlotRender(_, slot) 25 | local slotPtr = GetPtrHash(slot) 26 | local prevAnimation = slotAnimations[slotPtr] 27 | local currentAnimation = slot:GetSprite():GetAnimation() 28 | 29 | if prevAnimation and prevAnimation ~= currentAnimation then 30 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SLOT_ANIMATION_CHANGED, slot, prevAnimation, currentAnimation) 31 | end 32 | 33 | slotAnimations[slotPtr] = currentAnimation 34 | end 35 | TSIL.__AddInternalCallback( 36 | "SLOT_ANIMATION_CHANGED_CALLBACK_POST_SLOT_RENDER", 37 | TSIL.Enums.CustomCallback.POST_SLOT_RENDER, 38 | OnSlotRender 39 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SlotCallbacks/SlotCollisionCallback.lua: -------------------------------------------------------------------------------- 1 | --##PRE_SLOT_COLLISION 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.PRE_SLOT_COLLISION, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE, 6 | TSIL.Enums.CallbackOptionalArgType.PLAYER_TYPE_VARIANT 7 | ) 8 | 9 | 10 | ---@param player EntityPlayer 11 | ---@param collider Entity 12 | local function OnPlayerCollision(_, player, collider) 13 | if collider.Type == EntityType.ENTITY_SLOT then 14 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.PRE_SLOT_COLLISION, collider, player) 15 | end 16 | end 17 | TSIL.__AddInternalCallback( 18 | "PRE_SLOT_COLLISION_CALLBACK_PRE_PLAYER_COLLISION", 19 | ModCallbacks.MC_PRE_PLAYER_COLLISION, 20 | OnPlayerCollision 21 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SlotCallbacks/SlotInitCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SLOT_INIT 2 | 3 | TSIL.__RegisterCustomCallback( 4 | TSIL.Enums.CustomCallback.POST_SLOT_INIT, 5 | TSIL.Enums.CallbackReturnMode.NONE, 6 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 7 | ) 8 | 9 | local function OnSlotUpdate(_, slot) 10 | if slot.FrameCount ~= 1 then return end 11 | 12 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SLOT_INIT, slot) 13 | end 14 | TSIL.__AddInternalCallback( 15 | "SLOT_INIT_CALLBACK_SLOT_UPDATE", 16 | TSIL.Enums.CustomCallback.POST_SLOT_UPDATE, 17 | OnSlotUpdate 18 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SlotCallbacks/SlotPrizeCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SLOT_PRIZE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_SLOT_PRIZE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | local function OnSlotUpdate(_, slot) 9 | local slotSpr = slot:GetSprite() 10 | 11 | if not slotSpr:IsEventTriggered("Prize") and not slotSpr:IsEventTriggered("Disappear") then return end 12 | 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SLOT_PRIZE, slot, slotSpr:IsEventTriggered("Disappear")) 14 | end 15 | TSIL.__AddInternalCallback( 16 | "SLOT_PRIZE_CALLBACK_SLOT_UPDATE", 17 | TSIL.Enums.CustomCallback.POST_SLOT_UPDATE, 18 | OnSlotUpdate 19 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SlotCallbacks/SlotRenderCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SLOT_RENDER 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_SLOT_RENDER, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnRender() 10 | local slots = Isaac.FindByType(EntityType.ENTITY_SLOT) 11 | 12 | for _, slot in ipairs(slots) do 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SLOT_RENDER, slot) 14 | end 15 | end 16 | TSIL.__AddInternalCallback( 17 | "SLOT_RENDER_CALLBACK_POST_RENDER", 18 | ModCallbacks.MC_POST_RENDER, 19 | OnRender 20 | ) -------------------------------------------------------------------------------- /CustomCallbacks/SlotCallbacks/SlotUpdateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_SLOT_UPDATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_SLOT_UPDATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | local function OnFrameUpdate() 10 | local slots = TSIL.EntitySpecific.GetSlots() 11 | 12 | for _, slot in ipairs(slots) do 13 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_SLOT_UPDATE, slot) 14 | end 15 | end 16 | TSIL.__AddInternalCallback( 17 | "SLOT_UPDATE_CALLBACK_POST_UPDATE", 18 | ModCallbacks.MC_POST_UPDATE, 19 | OnFrameUpdate 20 | ) -------------------------------------------------------------------------------- /CustomCallbacks/TearCallbacks/TearInitLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_TEAR_INIT_LATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_TEAR_INIT_LATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param tear EntityTear 10 | local function OnTearUpdate(_, tear) 11 | local hasTriggered = TSIL.Entities.GetEntityData( 12 | TSIL.__MOD, 13 | tear, 14 | "hasTriggered_TEAR_INIT_LATE_CALLBACK" 15 | ) 16 | if hasTriggered then return end 17 | 18 | TSIL.Entities.SetEntityData( 19 | TSIL.__MOD, 20 | tear, 21 | "hasTriggered_TEAR_INIT_LATE_CALLBACK", 22 | true 23 | ) 24 | 25 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_TEAR_INIT_LATE, tear) 26 | end 27 | TSIL.__AddInternalCallback( 28 | "TEAR_INIT_LATE_CALLBACK_ON_TEAR_UPDATE", 29 | ModCallbacks.MC_POST_TEAR_UPDATE, 30 | OnTearUpdate 31 | ) -------------------------------------------------------------------------------- /CustomCallbacks/TearCallbacks/TearInitVeryLateCallback.lua: -------------------------------------------------------------------------------- 1 | --##POST_TEAR_INIT_VERY_LATE 2 | TSIL.__RegisterCustomCallback( 3 | TSIL.Enums.CustomCallback.POST_TEAR_INIT_VERY_LATE, 4 | TSIL.Enums.CallbackReturnMode.NONE, 5 | TSIL.Enums.CallbackOptionalArgType.ENTITY_TYPE_VARIANT_SUBTYPE 6 | ) 7 | 8 | 9 | ---@param tear EntityTear 10 | local function OnTearUpdate(_, tear) 11 | local timesTriggered = TSIL.Entities.GetEntityData( 12 | TSIL.__MOD, 13 | tear, 14 | "timesTriggered_TEAR_INIT_VERY_LATE_CALLBACK" 15 | ) 16 | if not timesTriggered then 17 | timesTriggered = 0 18 | end 19 | 20 | if timesTriggered > 1 then return end 21 | 22 | timesTriggered = timesTriggered + 1 23 | TSIL.Entities.SetEntityData( 24 | TSIL.__MOD, 25 | tear, 26 | "timesTriggered_TEAR_INIT_VERY_LATE_CALLBACK", 27 | timesTriggered 28 | ) 29 | 30 | if timesTriggered == 2 then 31 | TSIL.__TriggerCustomCallback(TSIL.Enums.CustomCallback.POST_TEAR_INIT_VERY_LATE, tear) 32 | end 33 | end 34 | TSIL.__AddInternalCallback( 35 | "TEAR_INIT_VERY_LATE_CALLBACK_ON_TEAR_UPDATE", 36 | ModCallbacks.MC_POST_TEAR_UPDATE, 37 | OnTearUpdate 38 | ) -------------------------------------------------------------------------------- /CustomItemPools/IsInPool.lua: -------------------------------------------------------------------------------- 1 | --##use CustomItemPools/RegisterItemPool.lua 2 | 3 | ---Helper function to check if a certain collectible is in a custom item pool. 4 | ---@param customItemPoolType integer 5 | ---@param collectibleType CollectibleType 6 | ---@return boolean 7 | function TSIL.CustomItemPools.IsCollectibleInCustomPool(customItemPoolType, collectibleType) 8 | local itemPoolCollectibles = TSIL.CustomItemPools.GetCollectibleEntriesInItemPool(customItemPoolType) 9 | 10 | return TSIL.Utils.Tables.Some(itemPoolCollectibles, function (value) 11 | return value.Collectible == collectibleType 12 | end) 13 | end -------------------------------------------------------------------------------- /CustomItemPools/RemoveCollectible.lua: -------------------------------------------------------------------------------- 1 | --##use CustomItemPools/RegisterItemPool.lua 2 | 3 | ---Helper function to remove an item from the given custom item pool. 4 | --- 5 | ---Will return true if the item was in the pool before being removed. 6 | ---@param customItemPoolType integer 7 | ---@param collectibleType CollectibleType 8 | ---@param decreaseBy? number @ If provided, it'll remove the weight from the item instead of removing the item completely 9 | ---@return boolean 10 | function TSIL.CustomItemPools.RemoveCollectible(customItemPoolType, collectibleType, decreaseBy) 11 | ---@type CustomItemPoolCollectible[][] 12 | local customItemPools = TSIL.SaveManager.GetPersistentVariable( 13 | TSIL.__MOD, 14 | "itemPools_CUSTOM_ITEM_POOLS" 15 | ) 16 | local itemPoolCollectibles = customItemPools[customItemPoolType] 17 | 18 | if not itemPoolCollectibles then 19 | error("There is no registered item pool with that id: " .. customItemPoolType) 20 | end 21 | 22 | local wasInPool = false 23 | 24 | for index, itemPoolCollectible in ipairs(itemPoolCollectibles) do 25 | if itemPoolCollectible.Collectible == collectibleType then 26 | wasInPool = true 27 | 28 | if decreaseBy then 29 | itemPoolCollectible.Weight = itemPoolCollectible.Weight - decreaseBy 30 | if itemPoolCollectible.Weight < itemPoolCollectible.RemoveOn then 31 | table.remove(itemPoolCollectibles, index) 32 | end 33 | else 34 | table.remove(itemPoolCollectibles, index) 35 | end 36 | 37 | break 38 | end 39 | end 40 | 41 | return wasInPool 42 | end -------------------------------------------------------------------------------- /Debug/GetTime.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get the current time for benchmarking / profiling purposes. 2 | --- 3 | ---The return value will either be in seconds or milliseconds, depending on if the "--luadebug" flag 4 | ---is turned on or not. 5 | --- 6 | ---If the "--luadebug" flag is present, then this function will use the `socket.gettime` method, 7 | ---which returns the epoch timestamp in seconds (e.g. "1640320492.5779"). This is preferable over 8 | ---the more conventional `Isaac.GetTime` method, since it has one extra decimal point of precision. 9 | --- 10 | ---If the "--luadebug" flag is not present, then this function will use the `Isaac.GetTime` method, 11 | ---which returns the number of milliseconds since the computer's operating system was started (e.g. 12 | ---"739454963"). 13 | ---@return number 14 | function TSIL.Debug.GetTime() 15 | if TSIL.Debug.IsLuaDebugEnabled() then 16 | local ok, requiredSocket = pcall(require, "socket") 17 | 18 | if ok then 19 | return requiredSocket.gettime() 20 | end 21 | end 22 | 23 | return Isaac.GetTime() 24 | end -------------------------------------------------------------------------------- /Debug/GetTraceback.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get a stack trace. 2 | --- 3 | ---This will only work if the `--luadebug` launch option is enabled. 4 | ---@return string 5 | function TSIL.Debug.GetTraceback() 6 | if debug ~= nil then 7 | return debug.traceback() 8 | else 9 | return 'stack traceback:\n(the "--luadebug" flag is not enabled)' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Debug/IsDebugModeActive.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a debug mode is active. 2 | ---@param mode DebugMode 3 | ---@return boolean 4 | function TSIL.Debug.IsDebugModeActive(mode) 5 | local cmd = "debug " .. mode 6 | 7 | local result = Isaac.ExecuteCommand(cmd) 8 | Isaac.ExecuteCommand(cmd) 9 | 10 | return result == "Disabled debug flag." 11 | end -------------------------------------------------------------------------------- /Debug/IsLuaDebugEnabled.lua: -------------------------------------------------------------------------------- 1 | ---Players can boot the game with an launch option called "--luadebug", which will enable additional 2 | ---functionality that is considered to be unsafe. For more information about this flag, see the 3 | ---wiki: https://bindingofisaacrebirth.fandom.com/wiki/Launch_Options 4 | --- 5 | ---When this flag is enabled, the global environment will be slightly different. The differences are 6 | ---documented here: https://wofsauge.github.io/IsaacDocs/rep/Globals.html 7 | --- 8 | ---This function uses the `package` global variable as a proxy to determine if the "--luadebug" flag 9 | ---is enabled or not. 10 | ---@return boolean 11 | function TSIL.Debug.IsLuaDebugEnabled() 12 | return not not _LUADEBUG 13 | end -------------------------------------------------------------------------------- /Debug/Traceback.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to print a stack trace to the "log.txt" file, similar to JavaScript's 2 | ---`console.trace` function. 3 | ---This will only work if the `--luadebug` launch option is enabled 4 | function TSIL.Debug.Traceback() 5 | local tracebackOutput = TSIL.Debug.GetTraceback() 6 | TSIL.Log.Log(tracebackOutput) 7 | end -------------------------------------------------------------------------------- /Dimensions/GetDimension.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the current dimension. 2 | ---@return Dimension @ If something fails, `Dimension.CURRENT` will be returned 3 | function TSIL.Dimensions.GetDimension() 4 | local level = Game():GetLevel() 5 | local roomIndex = level:GetCurrentRoomIndex() 6 | 7 | for i = 0, 2 do 8 | if GetPtrHash(level:GetRoomByIdx(roomIndex, i)) == GetPtrHash(level:GetRoomByIdx(roomIndex, -1)) then 9 | ---@diagnostic disable-next-line: return-type-mismatch 10 | return i 11 | end 12 | end 13 | 14 | return TSIL.Enums.Dimension.CURRENT 15 | end -------------------------------------------------------------------------------- /Dimensions/InDimension.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if the players are in a given dimension. 2 | ---@param dimension Dimension 3 | ---@return boolean 4 | function TSIL.Dimensions.InDimension(dimension) 5 | return TSIL.Dimensions.GetDimension() == dimension 6 | end -------------------------------------------------------------------------------- /Direction/AngleToDirection.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to convert a given amount of angle degrees into the corresponding `Direction` enum. 2 | ---@param angleDegrees number 3 | ---@return Direction 4 | function TSIL.Direction.AngleToDirection(angleDegrees) 5 | local positiveDegrees = angleDegrees; 6 | while positiveDegrees < 0 do 7 | positiveDegrees = positiveDegrees + 360 8 | end 9 | local normalizedDegrees = positiveDegrees % 360; 10 | 11 | if normalizedDegrees < 45 then 12 | return Direction.RIGHT 13 | end 14 | 15 | if normalizedDegrees < 135 then 16 | return Direction.DOWN 17 | end 18 | 19 | if normalizedDegrees < 225 then 20 | return Direction.LEFT 21 | end 22 | 23 | if normalizedDegrees < 315 then 24 | return Direction.UP 25 | end 26 | 27 | return Direction.RIGHT 28 | end 29 | -------------------------------------------------------------------------------- /Direction/DirectionToDegrees.lua: -------------------------------------------------------------------------------- 1 | local DIRECTION_TO_ANGLE = { 2 | [Direction.NO_DIRECTION] = 0, 3 | [Direction.LEFT] = 180, 4 | [Direction.UP] = 270, 5 | [Direction.RIGHT] = 0, 6 | [Direction.DOWN] = 90, 7 | } 8 | 9 | --- Helper function to get the corresponding angle degrees from a `Direction` enum. 10 | ---@param direction Direction 11 | ---@return integer 12 | function TSIL.Direction.DirectionToDegrees(direction) 13 | return DIRECTION_TO_ANGLE[direction] 14 | end 15 | -------------------------------------------------------------------------------- /Direction/DirectionToVector.lua: -------------------------------------------------------------------------------- 1 | local DIRECTION_TO_VECTOR = { 2 | [Direction.NO_DIRECTION] = Vector.Zero, 3 | [Direction.LEFT] = Vector(-1, 0), 4 | [Direction.UP] = Vector(0, -1), 5 | [Direction.RIGHT] = Vector(1, 0), 6 | [Direction.DOWN] = Vector(0, 1), 7 | } 8 | 9 | --- Helper function to get a Vector pointing in a given Direction. 10 | ---@param direction Direction 11 | ---@return Vector 12 | function TSIL.Direction.DirectionToVector(direction) 13 | return DIRECTION_TO_VECTOR[direction] 14 | end -------------------------------------------------------------------------------- /Doors/CloseDoors.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to open a door instantly without playing its open animation. 2 | ---@param door GridEntityDoor 3 | function TSIL.Doors.CloseDoorFast(door) 4 | door.State = DoorState.STATE_CLOSED 5 | 6 | local sprite = door:GetSprite() 7 | sprite:Play("Closed", true) 8 | end 9 | 10 | 11 | --- Helper funciton to close all doors in the current room. 12 | ---@param playAnim boolean @ If set to false, the doors won't play the close animation. 13 | function TSIL.Doors.CloseAllDoors(playAnim) 14 | local doors = TSIL.Doors.GetDoors() 15 | 16 | for _, door in ipairs(doors) do 17 | if playAnim then 18 | door:Close(true) 19 | else 20 | TSIL.Doors.CloseDoorFast(door) 21 | end 22 | end 23 | end 24 | 25 | 26 | --- Helper function to reset an unlocked door back to its locked state. 27 | ---@param door GridEntityDoor 28 | function TSIL.Doors.LockDoor(door) 29 | local level = Game():GetLevel() 30 | 31 | local roomDescriptor = level:GetRoomByIdx(door.TargetRoomIndex) 32 | roomDescriptor.VisitedCount = 0 33 | 34 | door:SetVariant(DoorVariant.DOOR_LOCKED) 35 | door:SetLocked(true) 36 | door:Close(true) 37 | end -------------------------------------------------------------------------------- /Doors/DoorSlotFlag.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get a door slot flag from a door slot. 2 | ---@param doorSlot DoorSlot 3 | ---@return integer 4 | function TSIL.Doors.DoorSlotToDoorSlotFlag(doorSlot) 5 | return 1 << doorSlot 6 | end 7 | 8 | 9 | --- Helper function to convert the provided door slots into a door slot bitmask. 10 | ---@param ... DoorSlot 11 | ---@return integer 12 | function TSIL.Doors.DoorSlotsToDoorSlotBitMask(...) 13 | local doorSlots = {...} 14 | local doorSlotBitMask = 0 15 | 16 | for _, doorSlot in ipairs(doorSlots) do 17 | doorSlotBitMask = doorSlotBitMask | TSIL.Doors.DoorSlotToDoorSlotFlag(doorSlot) 18 | end 19 | 20 | return doorSlotBitMask 21 | end 22 | 23 | 24 | --- Helper function to get the door slots corresponding to a door slot bit mask. 25 | ---@param doorSlotBitMask integer 26 | ---@return DoorSlot[] 27 | function TSIL.Doors.GetDoorSlotsFromDoorSlotBitMask(doorSlotBitMask) 28 | local doorSlots = {} 29 | 30 | for doorSlot = 0, DoorSlot.NUM_DOOR_SLOTS-1, 1 do 31 | ---@diagnostic disable-next-line: unknown-cast-variable 32 | ---@cast doorSlot DoorSlot 33 | if TSIL.Utils.Flags.HasFlags(doorSlotBitMask, TSIL.Doors.DoorSlotToDoorSlotFlag(doorSlot)) then 34 | doorSlots[#doorSlots+1] = doorSlot 35 | end 36 | end 37 | 38 | return doorSlots 39 | end -------------------------------------------------------------------------------- /Doors/DoorSlotToDirection.lua: -------------------------------------------------------------------------------- 1 | local DOOR_SLOT_TO_DIRECTION = { 2 | [DoorSlot.NO_DOOR_SLOT] = Direction.NO_DIRECTION, 3 | [DoorSlot.LEFT0] = Direction.LEFT, 4 | [DoorSlot.UP0] = Direction.UP, 5 | [DoorSlot.RIGHT0] = Direction.RIGHT, 6 | [DoorSlot.DOWN0] = Direction.DOWN, 7 | [DoorSlot.LEFT1] = Direction.LEFT, 8 | [DoorSlot.UP1] = Direction.UP, 9 | [DoorSlot.RIGHT1] = Direction.RIGHT, 10 | [DoorSlot.DOWN1] = Direction.DOWN, 11 | } 12 | 13 | 14 | --- Helper function to get the direction corresponding to a given door slot. 15 | ---@param doorSlot DoorSlot 16 | ---@return Direction 17 | function TSIL.Doors.DoorSlotToDirection(doorSlot) 18 | return DOOR_SLOT_TO_DIRECTION[doorSlot] 19 | end -------------------------------------------------------------------------------- /Doors/GetDoorEnterPosition.lua: -------------------------------------------------------------------------------- 1 | local DIRECTION_TO_VECTOR = { 2 | [Direction.NO_DIRECTION] = Vector.Zero, 3 | [Direction.LEFT] = Vector(-1, 0), 4 | [Direction.UP] = Vector(0, -1), 5 | [Direction.RIGHT] = Vector(1, 0), 6 | [Direction.DOWN] = Vector(0, 1) 7 | } 8 | 9 | 10 | --- Helper function to get the offset from a door position that a player will enter a room at. 11 | ---@param doorSlot any 12 | ---@return Vector 13 | function TSIL.Doors.GetDoorSlotEnterPositionOffset(doorSlot) 14 | local direction = TSIL.Doors.DoorSlotToDirection(doorSlot) 15 | local vector = DIRECTION_TO_VECTOR[direction] 16 | 17 | local oppositeVector = vector * -1 18 | 19 | return oppositeVector * 40 20 | end 21 | 22 | 23 | --- Helper function to get the position that a player will enter a room at corresponding to a door slot. 24 | ---@param doorSlot DoorSlot 25 | ---@return Vector 26 | function TSIL.Doors.GetDoorSlotEnterPosition(doorSlot) 27 | local room = Game():GetRoom() 28 | 29 | local position = room:GetDoorSlotPosition(doorSlot) 30 | local offset = TSIL.Doors.GetDoorSlotEnterPositionOffset(doorSlot) 31 | 32 | return position + offset 33 | end 34 | 35 | 36 | --- Helper function to get the position that a player will enter a room at corresponding to a door. 37 | ---@param door GridEntityDoor 38 | ---@return Vector 39 | function TSIL.Doors.GetDoorEnterPosition(door) 40 | local offset = TSIL.Doors.GetDoorSlotEnterPositionOffset(door.Slot) 41 | return door.Position + offset 42 | end -------------------------------------------------------------------------------- /Doors/GetDoors.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to return all doors in the current room. 2 | --- 3 | --- You can optionally specify one or more room types to return only the doors 4 | --- that match the specified room types. 5 | ---@param ... RoomType 6 | ---@return GridEntityDoor[] 7 | function TSIL.Doors.GetDoors(...) 8 | local possibleRoomTypes = {...} 9 | local room = Game():GetRoom() 10 | local doors = {} 11 | 12 | for i = 0, DoorSlot.NUM_DOOR_SLOTS-1, 1 do 13 | ---@cast i DoorSlot 14 | local door = room:GetDoor(i) 15 | 16 | if door and (#possibleRoomTypes == 0 or TSIL.Utils.Tables.IsIn(possibleRoomTypes, door.TargetRoomType)) then 17 | doors[#doors+1] = door 18 | end 19 | end 20 | 21 | return doors 22 | end 23 | 24 | 25 | --- Helper function to return all doors in the current room that lead to 26 | --- a given room index. 27 | ---@param ... integer 28 | ---@return GridEntityDoor[] 29 | function TSIL.Doors.GetDoorsToRoomIndex(...) 30 | local possibleRoomIndexes = {...} 31 | local room = Game():GetRoom() 32 | local doors = {} 33 | 34 | for i = 0, DoorSlot.NUM_DOOR_SLOTS-1, 1 do 35 | ---@cast i DoorSlot 36 | local door = room:GetDoor(i) 37 | 38 | if door and (#possibleRoomIndexes == 0 or TSIL.Utils.Tables.IsIn(possibleRoomIndexes, door.TargetRoomIndex)) then 39 | doors[#doors+1] = door 40 | end 41 | end 42 | 43 | return doors 44 | end -------------------------------------------------------------------------------- /Doors/GetSpecificDoors.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the angel room door in the current room. 2 | --- If there isn't any, returns nil. 3 | ---@return GridEntityDoor? 4 | function TSIL.Doors.GetAngelRoomDoor() 5 | local angelDoors = TSIL.Doors.GetDoors(RoomType.ROOM_ANGEL) 6 | return angelDoors[1] 7 | end 8 | 9 | 10 | --- Helper function to get the devil room door in the current room. 11 | --- If there isn't any, returns nil. 12 | ---@return GridEntityDoor? 13 | function TSIL.Doors.GetDevilRoomDoor() 14 | local devilDoors = TSIL.Doors.GetDoors(RoomType.ROOM_DEVIL) 15 | return devilDoors[1] 16 | end 17 | 18 | 19 | --- Helper function to get an angel or devil room door in the current room. 20 | --- 21 | --- Note that if there are both an angel and devil room door it'll only return the one in the lowest door slot. 22 | ---@return GridEntityDoor? 23 | function TSIL.Doors.GetAngelOrDevilRoomDoor() 24 | local angelDevilDoors = TSIL.Doors.GetDoors(RoomType.ROOM_ANGEL, RoomType.ROOM_DEVIL) 25 | return angelDevilDoors[1] 26 | end 27 | 28 | 29 | --- Helper function to get the door that leads to the blue womb entrance in the current room. 30 | ---@return GridEntityDoor? 31 | function TSIL.Doors.GetBlueWombDoor() 32 | local blueWombDoors = TSIL.Doors.GetDoorsToRoomIndex(GridRooms.ROOM_BLUE_WOOM_IDX) 33 | return blueWombDoors[1] 34 | end 35 | 36 | 37 | --- Helper function to get the door that leads to a repentance secret exit in the current room. 38 | ---@return GridEntityDoor? 39 | function TSIL.Doors.GetSecretExitDoor() 40 | local blueWombDoors = TSIL.Doors.GetDoorsToRoomIndex(GridRooms.ROOM_SECRET_EXIT_IDX) 41 | return blueWombDoors[1] 42 | end -------------------------------------------------------------------------------- /Doors/IsDoorSlotInRoomShape.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if a given door slot can be present in a given room shape. 2 | ---@param doorSlot DoorSlot 3 | ---@param roomShape RoomShape 4 | ---@return boolean 5 | function TSIL.Doors.IsDoorSlotInRoomShape(doorSlot, roomShape) 6 | local doorSlotsInRoomShape = TSIL.Doors.GetDoorSlotsForRoomShape(roomShape) 7 | return TSIL.Utils.Tables.IsIn(doorSlotsInRoomShape, doorSlot) 8 | end -------------------------------------------------------------------------------- /Doors/OpenDoors.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to open a door instantly without playing its open animation. 2 | ---@param door GridEntityDoor 3 | function TSIL.Doors.OpenDoorFast(door) 4 | door.State = DoorState.STATE_OPEN 5 | 6 | local sprite = door:GetSprite() 7 | sprite:Play("Opened", true) 8 | end 9 | 10 | 11 | --- Helper funciton to open all doors in the current room 12 | ---@param playAnim boolean @ If set to false, the doors won't play the open animation. 13 | function TSIL.Doors.OpenAllDoors(playAnim) 14 | local doors = TSIL.Doors.GetDoors() 15 | 16 | for _, door in ipairs(doors) do 17 | if playAnim then 18 | door:Open() 19 | else 20 | TSIL.Doors.OpenDoorFast(door) 21 | end 22 | end 23 | end -------------------------------------------------------------------------------- /Doors/RemoveDoors.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to remove a single door 2 | ---@param door GridEntityDoor 3 | function TSIL.Doors.RemoveDoor(door) 4 | local room = Game():GetRoom() 5 | room:RemoveDoor(door.Slot) 6 | end 7 | 8 | 9 | --- Helper function to remove all the given doors 10 | ---@param doors GridEntityDoor[] 11 | function TSIL.Doors.RemoveDoors(doors) 12 | for _, door in ipairs(doors) do 13 | TSIL.Doors.RemoveDoor(door) 14 | end 15 | end 16 | 17 | 18 | --- Helper function to remove all doors of the given room types. 19 | ---@param ... RoomType @ If no room type is specified, all doors will get removed. 20 | function TSIL.Doors.RemoveAllDoorsOfType(...) 21 | local doors = TSIL.Doors.GetDoors(...) 22 | TSIL.Doors.RemoveDoors(doors) 23 | end -------------------------------------------------------------------------------- /Doors/UnusedDoorSlots.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get all unused door slots in the current room. 2 | --- 3 | --- Useful to spawn custom doors. 4 | ---@return DoorSlot[] 5 | function TSIL.Doors.GetUnusedDoorSlots() 6 | local room = Game():GetRoom() 7 | local unusedDoorSlots = {} 8 | 9 | for i = 0, DoorSlot.NUM_DOOR_SLOTS-1, 1 do 10 | ---@cast i DoorSlot 11 | if room:IsDoorSlotAllowed(i) and not room:GetDoor(i) then 12 | unusedDoorSlots[#unusedDoorSlots+1] = i 13 | end 14 | end 15 | 16 | return unusedDoorSlots 17 | end 18 | 19 | 20 | --- Helper function to check if the current room has any unused door slots. 21 | ---@return boolean 22 | function TSIL.Doors.HasUnusedDoorSlot() 23 | local unusedDoorSots = TSIL.Doors.GetUnusedDoorSlots() 24 | return #unusedDoorSots > 0 25 | end -------------------------------------------------------------------------------- /Effects/IsCloseEnoughToTriggerDiceFloor.lua: -------------------------------------------------------------------------------- 1 | local DICE_FLOOR_TRIGGER_SQUARE_SIZE = 75 2 | 3 | ---Helper function to check if a player is close enough to a dice floor to trigger its effect. 4 | --- 5 | ---Unlike most entities in the game, dice floors have an square hitbox. 6 | ---@param entity Entity 7 | ---@param diceFloor EntityEffect 8 | function TSIL.Effects.IsCloseEnoughToTriggerDiceFloor(entity, diceFloor) 9 | local topLeft = diceFloor.Position + Vector(-DICE_FLOOR_TRIGGER_SQUARE_SIZE, -DICE_FLOOR_TRIGGER_SQUARE_SIZE) 10 | local topRight = diceFloor.Position + Vector(DICE_FLOOR_TRIGGER_SQUARE_SIZE, -DICE_FLOOR_TRIGGER_SQUARE_SIZE) 11 | local bottomRight = diceFloor.Position + Vector(DICE_FLOOR_TRIGGER_SQUARE_SIZE, DICE_FLOOR_TRIGGER_SQUARE_SIZE) 12 | 13 | return TSIL.Utils.Math.IsInRectangle(entity.Position, topLeft, topRight, bottomRight) 14 | end -------------------------------------------------------------------------------- /Entities/GetEntities.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get all of the entities in the room or all of the entities tht match a specific entity type / variant / sub-type. 2 | --- Due to bugs with `Isaac.FindInRadius`, this function uses `Isaac.GetRoomEntities`, which is more expensive but is also more robust. 3 | --- (If a matching entity type is provided, then `Isaac.FindByType` will be used instead.) 4 | ---@param entityType EntityType|integer? @Default: -1 | If specified, will only get the entities that match the type. Default is -1, which matches every type. 5 | ---@param variant integer? @Default: -1 | If specified, will only get the entities that match the variant. Default is -1, which matches every variant. 6 | ---@param subType integer? @Default: -1 | If specified, will only get the entities that match the sub-type. Default is -1, which matches every sub-type. 7 | ---@param ignoreFriendly boolean? @Default: false | If set to true, it will exclude friendly NPCs from being returned. Will only be taken into account if the `entityType` is specified. 8 | ---@return Entity[] 9 | function TSIL.Entities.GetEntities(entityType, variant, subType, ignoreFriendly) 10 | entityType = entityType or -1 11 | variant = variant or -1 12 | subType = subType or -1 13 | 14 | if ignoreFriendly == nil then 15 | ignoreFriendly = false 16 | end 17 | 18 | if entityType == -1 then 19 | return Isaac.GetRoomEntities() 20 | end 21 | 22 | --- @diagnostic disable-next-line: param-type-mismatch 23 | return Isaac.FindByType(entityType, variant, subType, ignoreFriendly) 24 | end -------------------------------------------------------------------------------- /Entities/IsCollidingWithGrid.lua: -------------------------------------------------------------------------------- 1 | --- Checks if an entity is colliding with a grid entity. 2 | --- If it does, returns the first grid entity it's colliding with, else returns nil. 3 | ---@param entity Entity 4 | ---@return GridEntity? 5 | function TSIL.Entities.IsCollidingWithGrid(entity) 6 | local room = Game():GetRoom() 7 | 8 | for x = -1, 1, 1 do 9 | for y = -1, 1, 1 do 10 | if x~= 0 or y~=0 then 11 | local gridPosition = entity.Position + Vector(x*40, y*40) 12 | local gridEntity = room:GetGridEntityFromPos(gridPosition) 13 | 14 | if gridEntity and 15 | TSIL.Entities.CanCollideWithGridEntity(entity, gridEntity) and 16 | TSIL.Utils.Math.IsCircleIntersectingWithRectangle(gridEntity.Position, Vector(40, 40), entity.Position, entity.Size) then 17 | return gridEntity 18 | end 19 | end 20 | end 21 | end 22 | 23 | return nil 24 | end -------------------------------------------------------------------------------- /Entities/Spawn.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to spawn an entity. Use this instead of the `Isaac.Spawn` method if you do not 2 | ---need to specify the velocity or spawner. 3 | ---@param entityType EntityType 4 | ---@param variant integer 5 | ---@param subType integer 6 | ---@param position Vector 7 | ---@param velocity Vector? 8 | ---@param spawner Entity? 9 | ---@param seedOrRNG integer | RNG? 10 | ---@return Entity 11 | function TSIL.Entities.Spawn(entityType, variant, subType, position, velocity, spawner, seedOrRNG) 12 | velocity = velocity or Vector.Zero 13 | 14 | if seedOrRNG == nil then 15 | return Isaac.Spawn(entityType, variant, subType, position, velocity, spawner) 16 | end 17 | 18 | ---@type integer 19 | local seed 20 | 21 | if TSIL.IsaacAPIClass.IsRNG(seedOrRNG) then 22 | seed = seedOrRNG:Next() 23 | else 24 | ---@cast seedOrRNG integer 25 | seed = seedOrRNG 26 | end 27 | 28 | return Game():Spawn(entityType, variant, position, velocity, spawner, subType, seed) 29 | end -------------------------------------------------------------------------------- /Enums/AmbushType.lua: -------------------------------------------------------------------------------- 1 | ---@enum AmbushType 2 | TSIL.Enums.AmbushType = { 3 | CHALLENGE_ROOM = 0, 4 | BOSS_RUSH = 1 5 | } -------------------------------------------------------------------------------- /Enums/CallbackOptionalArgType.lua: -------------------------------------------------------------------------------- 1 | ---Used when registering a custom callback using `TSIL.__RegisterCustomCallback()`. 2 | --- 3 | ---Defines how the callback filtering should treat each parameter. 4 | ---@enum CallbackOptionalArgType 5 | TSIL.Enums.CallbackOptionalArgType = { 6 | --Directly checks for equality using `==` 7 | GENERIC = 0, 8 | --Skips this argument when checking 9 | NONE = 1, 10 | 11 | ENTITY_TYPE = 2, 12 | ENTITY_TYPE_VARIANT = 3, 13 | ENTITY_TYPE_VARIANT_SUBTYPE = 4, 14 | ENTITY_VARIANT_SUBTYPE = 5, 15 | ENTITY_SUBTYPE = 6, 16 | 17 | GRID_TYPE = 7, 18 | GRID_TYPE_VARIANT = 8, 19 | GRID_VARIANT = 9, 20 | 21 | PLAYER_TYPE_VARIANT = 10, 22 | PLAYER_TYPE = 11, 23 | 24 | MOD_NAME = 12 25 | } -------------------------------------------------------------------------------- /Enums/CallbackReturnMode.lua: -------------------------------------------------------------------------------- 1 | ---Used when registering a custom callback using `TSIL.__RegisterCustomCallback()`. 2 | --- 3 | ---Defines how the returned values of the executed functions will be treated and 4 | ---what value is returned by `TSIL.__TriggerCustomCallback()`. 5 | ---@enum CallbackReturnMode 6 | TSIL.Enums.CallbackReturnMode = { 7 | --Returned values are ignored 8 | NONE = 0, 9 | --When a function returns a non nil value 10 | --the rest are skipped. 11 | SKIP_NEXT = 1, 12 | --The last function to return a non nil value 13 | --overwrites all other returned values 14 | LAST_WINS = 2, 15 | --When a function returns a non nil value, the returned 16 | --value is used as the first argument for the next functions. 17 | --If a table is returned, the first element of the table will 18 | --be the first argument, the second element will be the second 19 | --argument, etc. 20 | NEXT_ARGUMENT = 3 21 | } -------------------------------------------------------------------------------- /Enums/ConversionHeartSubType.lua: -------------------------------------------------------------------------------- 1 | ---@enum ConversionHeartSubType 2 | TSIL.Enums.ConversionHeartSubType = { 3 | BLACK = HeartSubType.HEART_BLACK, 4 | SOUL = HeartSubType.HEART_SOUL, 5 | } 6 | -------------------------------------------------------------------------------- /Enums/CustomReviveType.lua: -------------------------------------------------------------------------------- 1 | ---Used in the `PRE_CUSTOM_REVIVE` callback to determine how the player should revive. 2 | ---@enum CustomReviveType 3 | TSIL.Enums.CustomReviveType = { 4 | --Internally uses Soul of Lazarus 5 | SAME_ROOM = 0, 6 | 7 | --Internally uses 1 UP 8 | PREVIOUS_ROOM = 1 9 | } -------------------------------------------------------------------------------- /Enums/DebugMode.lua: -------------------------------------------------------------------------------- 1 | ---@enum DebugMode 2 | TSIL.Enums.DebugMode = { 3 | ENTITY_POSITIONS = 1, 4 | ---Shows grid and grid collision values 5 | SHOW_GRID = 2, 6 | INFINITE_HP = 3, 7 | HIGH_DAMAGE = 4, 8 | ROOM_INFO = 5, 9 | SHOW_HITBOX = 6, 10 | SHOW_DAMAGE_VALUES = 7, 11 | INFINITE_CHARGE = 8, 12 | HIGH_LUCK = 9, 13 | QUICK_KILL = 10, 14 | ---Shows grid and grid indexes 15 | SHOW_GRID_INFO = 11, 16 | PLAYER_INFO = 12, 17 | SHOW_GRID_COLLISION = 13, 18 | SHOW_LUA_MEMORY_USAGE = 14 19 | } -------------------------------------------------------------------------------- /Enums/Dimension.lua: -------------------------------------------------------------------------------- 1 | ---@enum Dimension 2 | TSIL.Enums.Dimension = { 3 | CURRENT = -1, 4 | MAIN = 0, 5 | SECONDARY = 1, 6 | DEATH_CERTIFICATE = 2 7 | } -------------------------------------------------------------------------------- /Enums/GridEntityStates.lua: -------------------------------------------------------------------------------- 1 | ---@enum LockState 2 | TSIL.Enums.LockState = { 3 | LOCKED = 0, 4 | UNLOCKED = 1, 5 | } 6 | 7 | 8 | ---@enum PoopState 9 | TSIL.Enums.PoopState = { 10 | UNDAMAGED = 0, 11 | ONE_QUARTER_DAMAGED = 250, 12 | TWO_QUARTERS_DAMAGED = 500, 13 | THREE_QUARTERS_DAMAGED = 750, 14 | DESTROYED = 1000 15 | } 16 | 17 | 18 | ---@enum RockState 19 | TSIL.Enums.RockState = { 20 | UNBROKEN = 1, 21 | BROKEN = 2, 22 | EXPLODING = 3, 23 | HALF_BROKEN = 4 24 | } 25 | 26 | 27 | ---@enum SpiderWebState 28 | TSIL.Enums.SpiderWebState = { 29 | UNBROKEN = 0, 30 | BROKEN = 1, 31 | } 32 | 33 | 34 | ---@enum TNTState 35 | TSIL.Enums.TNTState = { 36 | UNDAMAGED = 0, 37 | ONE_QUARTER_DAMAGED = 1, 38 | TWO_QUARTERS_DAMAGED = 2, 39 | THREE_QUARTERS_DAMAGED = 3, 40 | EXPLODED = 4 41 | } -------------------------------------------------------------------------------- /Enums/GridEntityVariants.lua: -------------------------------------------------------------------------------- 1 | ---@enum CrawlSpaceVariant 2 | TSIL.Enums.CrawlSpaceVariant = { 3 | NORMAL = 0, 4 | GREAT_GIDEON = 1, 5 | SECRET_SHOP = 2, 6 | PASSAGE_TO_BEGGINING_OF_FLOOR = 3, 7 | NULL = 4 8 | } 9 | 10 | ---@enum PitVariant 11 | TSIL.Enums.PitVariant = { 12 | NORMAL = 0, 13 | FISSURE_SPAWNER = 16 14 | } 15 | 16 | 17 | ---@enum PoopGridEntityVariant 18 | TSIL.Enums.PoopGridEntityVariant = { 19 | NORMAL = 0, 20 | RED = 1, 21 | CORN = 2, 22 | GOLDEN = 3, 23 | RAINBOW = 4, 24 | BLACK = 5, 25 | WHITE = 6, 26 | GIGA_TOP_LEFT = 7, 27 | GIGA_TOP_RIGHT = 8, 28 | GIGA_BOTTOM_LEFT = 9, 29 | GIGA_BOTTOM_RIGHT = 10, 30 | CHARMING = 11 31 | } 32 | 33 | 34 | ---@enum PressurePlateVariant 35 | TSIL.Enums.PressurePlateVariant = { 36 | PRESSURE_PLATE = 0, 37 | REWARD_PLATE = 1, 38 | GREED_PLATE = 2, 39 | RAIL_PLATE = 3, 40 | KILL_ALL_ENEMIES_PLATE = 9, 41 | SPAWN_ROCKS_PLATE = 10 42 | } 43 | 44 | 45 | ---@enum RockVariant 46 | TSIL.Enums.RockVariant = { 47 | NORMAL = 0, 48 | EVENT = 1 49 | } 50 | 51 | 52 | ---@enum StatueVariant 53 | TSIL.Enums.StatueVariant = { 54 | DEVIL = 0, 55 | ANGEL = 1 56 | } 57 | 58 | 59 | ---@enum TrapdoorVariant 60 | TSIL.Enums.TrapdoorVariant = { 61 | NORMAL = 0, 62 | VOID_PORTAL = 1 63 | } -------------------------------------------------------------------------------- /Enums/GridEntityXMLType.lua: -------------------------------------------------------------------------------- 1 | ---@enum GridEntityXMLType 2 | TSIL.Enums.GridEntityXMLType = { 3 | DECORATION = 0, 4 | ROCK = 1000, 5 | ROCK_BOMB = 1001, 6 | ROCK_ALT = 1002, 7 | ROCK_TINTED = 1003, 8 | ROCK_ALT_2 = 1008, 9 | ROCK_EVENT = 1009, 10 | ROCK_SPIKED = 1010, 11 | ROCK_GOLD = 1011, 12 | TNT = 1300, 13 | FIREPLACE = 1400, 14 | RED_FIREPLACE = 1410, 15 | POOP_RED = 1490, 16 | POOP_RAINBOW = 1494, 17 | POOP_CORN = 1495, 18 | POOP_GOLDEN = 1496, 19 | POOP_BLACK = 1497, 20 | POOP_WHITE = 1498, 21 | POOP_GIGA = 1499, 22 | POOP = 1500, 23 | POOP_CHARMING = 1501, 24 | BLOCK = 1900, 25 | PILLAR = 1901, 26 | SPIKES = 1930, 27 | SPIKES_ON_OFF = 1931, 28 | SPIDER_WEB = 1940, 29 | WALL = 1999, 30 | PIT = 3000, 31 | FISSURE_SPAWNER = 3001, 32 | PIT_EVENT = 3009, 33 | LOCK = 4000, 34 | PRESSURE_PLATE = 4500, 35 | STATUE_DEVIL = 5000, 36 | STATUE_ANGEL = 5001, 37 | TELEPORTER = 6100, 38 | TRAPDOOR = 9000, 39 | CRAWL_SPACE = 9100, 40 | GRAVITY = 10000 41 | } -------------------------------------------------------------------------------- /Enums/GridState.lua: -------------------------------------------------------------------------------- 1 | ---@enum PitState 2 | TSIL.Enums.PitState = { 3 | NORMAL = 0, 4 | 5 | --- Pits can become filled when nearby rocks are bombed into them. 6 | --- 7 | --- Note that the ladder collectible does not change the state to this. 8 | FILLED = 1, 9 | } 10 | -------------------------------------------------------------------------------- /Enums/HealthType.lua: -------------------------------------------------------------------------------- 1 | ---@enum HealthType 2 | TSIL.Enums.HealthType = { 3 | RED = 1, 4 | SOUL = 2, 5 | ETERNAL = 3, 6 | BLACK = 4, 7 | GOLDEN = 5, 8 | BONE = 6, 9 | ROTTEN = 7, 10 | BROKEN = 8, 11 | MAX_HEARTS = 9, 12 | } 13 | -------------------------------------------------------------------------------- /Enums/InventoryType.lua: -------------------------------------------------------------------------------- 1 | ---@enum InventoryType 2 | TSIL.Enums.InventoryType = { 3 | COLLECTIBLE = 1, 4 | TRINKET = 2 5 | } -------------------------------------------------------------------------------- /Enums/LineCheckMode.lua: -------------------------------------------------------------------------------- 1 | TSIL.Enums.LineCheckMode = { 2 | --- Stopped by pits and rocks (e.g. like a Gaper's behavior). 3 | NORMAL = 0, 4 | 5 | --- Same as MODE_NORMAL, but less resource-intensive. 6 | ECONOMIC = 1, 7 | 8 | --- Only blocked by walls and metal blocks. 9 | EXPLOSION = 2, 10 | 11 | --- Not blocked by pits. Used by enemies that shoot projectiles at you, such as Hosts. 12 | PROJECTILE = 3, 13 | } 14 | -------------------------------------------------------------------------------- /Enums/PillEffectType.lua: -------------------------------------------------------------------------------- 1 | ---@enum PillEffectType 2 | TSIL.Enums.PillEffectType = { 3 | NULL = -1, 4 | POSITIVE = 0, 5 | NEGATIVE = 1, 6 | NEUTRAL = 2, 7 | MODDED = 3 8 | } -------------------------------------------------------------------------------- /Enums/ProjectilesMode.lua: -------------------------------------------------------------------------------- 1 | ---@enum ProjectilesMode 2 | TSIL.Enums.ProjectilesMode = { 3 | ONE_PROJECTILE = 0, 4 | 5 | -- Uses params.Spread. 6 | TWO_PROJECTILES = 1, 7 | 8 | -- Uses params.Spread. 9 | THREE_PROJECTILES = 2, 10 | 11 | -- Uses params.Spread. 12 | THREE_PROJECTILES_SPREAD = 3, 13 | 14 | -- Uses params.Spread. 15 | FOUR_PROJECTILES = 4, 16 | 17 | -- Uses params.Spread. 18 | FIVE_PROJECTILES = 5, 19 | 20 | -- Uses velocity.X as speed. 21 | FOUR_PROJECTILES_PLUS_PATTERN = 6, 22 | 23 | -- Uses velocity.X as speed. 24 | FOUR_PROJECTILES_X_PATTERN = 7, 25 | 26 | -- Uses velocity.X as speed. 27 | EIGHT_PROJECTILES_STAR_PATTERN = 8, 28 | 29 | ---- Uses `velocity.X` as speed. 30 | ---- Uses `velocity.Y` as N. 31 | ---- To fire in an arc, use params.FireDirectionLimit and params.DotProductLimit. 32 | N_PROJECTILES_IN_CIRCLE = 9, 33 | } 34 | -------------------------------------------------------------------------------- /Enums/Serialization.lua: -------------------------------------------------------------------------------- 1 | ---@enum SerializationBrand 2 | TSIL.Enums.SerializationBrand = { 3 | BIT_SET_128 = "__BIT_SET_128", 4 | COLOR = "__COLOR", 5 | K_COLOR = "__K_COLOR", 6 | RNG = "__RNG", 7 | VECTOR = "__VECTOR", 8 | TABLE_WITH_NUMBER_KEYS = "__TABLE_WITH_NUMBER_KEYS" 9 | } 10 | 11 | ---@enum SerializationType 12 | TSIL.Enums.SerializationType = { 13 | NONE = 1, 14 | SERIALIZE = 2, 15 | DESERIALIZE = 3, 16 | } 17 | 18 | 19 | ---@enum CopyableIsaacAPIClassType 20 | TSIL.Enums.CopyableIsaacAPIClassType = { 21 | BIT_SET_128 = "BitSet128", 22 | COLOR = "Color", 23 | K_COLOR = "KColor", 24 | RNG = "RNG", 25 | VECTOR = "Vector", 26 | } -------------------------------------------------------------------------------- /Enums/ShockwaveSoundMode.lua: -------------------------------------------------------------------------------- 1 | ---@enum ShockwaveSoundMode 2 | TSIL.Enums.ShockwaveSoundMode = { 3 | NO_SOUND = 0, 4 | ON_CREATE = 1, 5 | LOOP = 2 6 | } -------------------------------------------------------------------------------- /Enums/StageID.lua: -------------------------------------------------------------------------------- 1 | ---@enum StageID 2 | TSIL.Enums.StageID = { 3 | SPECIAL_ROOMS = 0, 4 | BASEMENT = 1, 5 | CELLAR = 2, 6 | BURNING_BASEMENT = 3, 7 | CAVES = 4, 8 | CATACOMBS = 5, 9 | FLOODED_CAVES = 6, 10 | DEPTHS = 7, 11 | NECROPOLIS = 8, 12 | DANK_DEPTHS = 9, 13 | WOMB = 10, 14 | UTERO = 11, 15 | SCARRED_WOMB = 12, 16 | BLUE_WOMB = 13, 17 | SHEOL = 14, 18 | CATHEDRAL = 15, 19 | DARK_ROOM = 16, 20 | CHEST = 17, 21 | VOID = 26, 22 | DOWNPOUR = 27, 23 | DROSS = 28, 24 | MINES = 29, 25 | ASHPIT = 30, 26 | MAUSOLEUM = 31, 27 | GEHENNA = 32, 28 | CORPSE = 33, 29 | MORTIS = 34, 30 | HOME = 35, 31 | BACKWARDS = 36, 32 | } -------------------------------------------------------------------------------- /Enums/VariablePersistenceMode.lua: -------------------------------------------------------------------------------- 1 | ---@enum VariablePersistenceMode 2 | TSIL.Enums.VariablePersistenceMode = { 3 | --The save manager won't reset your variable 4 | NONE = 1, 5 | 6 | --The save manager will restore the default on a new room 7 | RESET_ROOM = 2, 8 | --The save manager will restore the default on a new level 9 | RESET_LEVEL = 3, 10 | --The save manager will restore the default on a new run 11 | RESET_RUN = 4, 12 | 13 | --The save manager will remove your variable on a new room 14 | REMOVE_ROOM = 5, 15 | --The save manager will remove your variable on a new level 16 | REMOVE_LEVEL = 6, 17 | --The save manager will remove your variable on a new run 18 | REMOVE_RUN = 7 19 | } -------------------------------------------------------------------------------- /Familiars/GetPlayerFamiliars.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get all familiars that belong to a given player. 2 | ---@param player EntityPlayer 3 | ---@return EntityFamiliar[] 4 | function TSIL.Familiars.GetPlayerFamiliars(player) 5 | local playerPtrHash = GetPtrHash(player) 6 | local familiars = TSIL.EntitySpecific.GetFamiliars() 7 | 8 | return TSIL.Utils.Tables.Filter(familiars, function (_, familiar) 9 | local familiarPlayerPtrHash = GetPtrHash(familiar.Player) 10 | return playerPtrHash == familiarPlayerPtrHash 11 | end) 12 | end -------------------------------------------------------------------------------- /Familiars/SirenHelper.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the corresponding "Siren Helper" entity for a stolen familiar. 2 | --- 3 | --- When The Siren boss "steals" your familiars, a hidden "Siren Helper" entity is spawned to control 4 | --- each familiar stolen. (Checking for the presence of this entity seems to be the only way to 5 | --- detect when the Siren steals a familiar.) 6 | ---@param familiar EntityFamiliar 7 | ---@return Entity? @ Returns the hidden "Siren Helper" entity corresponding to the given familiar, if it exists. Returns nil otherwise. 8 | function TSIL.Familiars.GetSirenHelper(familiar) 9 | local familiarPtrHash = GetPtrHash(familiar) 10 | 11 | local sirenHelpers = TSIL.Entities.GetEntities(EntityType.ENTITY_SIREN_HELPER) 12 | 13 | return TSIL.Utils.Tables.FindFirst(sirenHelpers, function (_, sirenHelper) 14 | return sirenHelper.Target and GetPtrHash(sirenHelper.Target) == familiarPtrHash 15 | end) 16 | end 17 | 18 | 19 | --- Helper function to check if the given familiar is being controlled by The Siren boss. 20 | ---@param familiar EntityFamiliar 21 | ---@return boolean 22 | function TSIL.Familiars.IsFamiliarStolenBySiren(familiar) 23 | local sirenHelper = TSIL.Familiars.GetSirenHelper(familiar) 24 | return sirenHelper ~= nil 25 | end -------------------------------------------------------------------------------- /GridEntities/GetCollidingEntitiesWithGridEntity.lua: -------------------------------------------------------------------------------- 1 | --- Returns all the entities that are colliding with a given grid entity. 2 | --- 3 | --- Note that this function won't work in the `POST_NEW_ROOM` callback, since 4 | --- entities don't have collision yet. 5 | ---@param gridEntity GridEntity 6 | ---@return Entity[] 7 | function TSIL.GridEntities.GetCollidingEntitiesWithGridEntity(gridEntity) 8 | local closeEntities = Isaac.FindInRadius(gridEntity.Position, 80) 9 | 10 | return TSIL.Utils.Tables.Filter(closeEntities, function (_, entity) 11 | return entity:CollidesWithGrid() 12 | and TSIL.Entities.CanCollideWithGridEntity(entity, gridEntity) 13 | and TSIL.Utils.Math.IsCircleIntersectingWithRectangle( 14 | gridEntity.Position, 15 | Vector(20, 20), 16 | entity.Position, 17 | entity.Size + 0.1 18 | ) 19 | end) 20 | end -------------------------------------------------------------------------------- /GridEntities/GetGridEntities.lua: -------------------------------------------------------------------------------- 1 | --- Returns a list with all grid entities in the room. 2 | ---@param ... GridEntityType 3 | ---@return GridEntity[] 4 | function TSIL.GridEntities.GetGridEntities(...) 5 | local gridEntityTypes = {...} 6 | local gridEntityTypesDict = TSIL.Utils.Tables.ConstructDictionaryFromTable(gridEntityTypes) 7 | 8 | ---@type GridEntity[] 9 | local gridEntities = {} 10 | 11 | local room = Game():GetRoom() 12 | 13 | for i = 0, room:GetGridSize() - 1, 1 do 14 | local gridEntity = room:GetGridEntity(i) 15 | 16 | if #gridEntityTypes == 0 or (gridEntity and gridEntityTypesDict[gridEntity:GetType()]) then 17 | gridEntities[#gridEntities+1] = gridEntity 18 | end 19 | end 20 | 21 | return gridEntities 22 | end 23 | 24 | 25 | --- Returns a map with all grid entities in the room indexed by their grid index. 26 | ---@param ... GridEntityType 27 | ---@return table 28 | function TSIL.GridEntities.GetGridEntitiesMap(...) 29 | local gridEntities = TSIL.GridEntities.GetGridEntities(...) 30 | ---@type table 31 | local gridEntitiesMap = {} 32 | 33 | TSIL.Utils.Tables.ForEach(gridEntities, function (_, gridEntity) 34 | gridEntitiesMap[gridEntity:GetGridIndex()] = gridEntity 35 | end) 36 | 37 | return gridEntitiesMap 38 | end -------------------------------------------------------------------------------- /GridEntities/GetSurroundingGridEntities.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get all grid entities around a grid index, not including itself. 2 | ---@param gridIndex GridEntity 3 | ---@return GridEntity[] 4 | function TSIL.GridEntities.GetSurroundingGridEntities(gridIndex) 5 | local room = Game():GetRoom() 6 | 7 | local gridWidth = TSIL.Rooms.GetRoomShapeGridWidth(room:GetRoomShape()) 8 | 9 | local gridIndex = gridIndex 10 | 11 | local surroundingGridIndexes = { 12 | gridIndex - gridWidth, --Top 13 | gridIndex + gridWidth, --Under 14 | } 15 | 16 | if gridIndex % gridWidth ~= 0 then 17 | surroundingGridIndexes[#surroundingGridIndexes+1] = gridIndex - 1 18 | surroundingGridIndexes[#surroundingGridIndexes+1] = gridIndex - gridWidth - 1 19 | surroundingGridIndexes[#surroundingGridIndexes+1] = gridIndex + gridWidth - 1 20 | end 21 | 22 | if gridIndex % gridWidth ~= gridWidth - 1 then 23 | surroundingGridIndexes[#surroundingGridIndexes+1] = gridIndex + 1 24 | surroundingGridIndexes[#surroundingGridIndexes+1] = gridIndex - gridWidth + 1 25 | surroundingGridIndexes[#surroundingGridIndexes+1] = gridIndex + gridWidth + 1 26 | end 27 | 28 | local validGridIndexes = TSIL.Utils.Tables.Filter(surroundingGridIndexes, function (_, adjacentGridIndex) 29 | return TSIL.GridEntities.IsGridIndexInRoom(adjacentGridIndex) 30 | end) 31 | 32 | local surroundingGridEntities = {} 33 | 34 | for _, adjacentGridIndex in ipairs(validGridIndexes) do 35 | local adjacentGridEntity = room:GetGridEntity(adjacentGridIndex) 36 | 37 | if adjacentGridEntity then 38 | surroundingGridEntities[#surroundingGridEntities+1] = adjacentGridEntity 39 | end 40 | end 41 | 42 | return surroundingGridEntities 43 | end -------------------------------------------------------------------------------- /GridEntities/GetTopLeftWall.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the grid index of the top left wall. 2 | ---@return integer 3 | function TSIL.GridEntities.GetTopLeftWallGridIndex() 4 | local room = Game():GetRoom() 5 | local gridSize = room:GetGridSize() 6 | local roomShape = room:GetRoomShape() 7 | 8 | for i = 0, gridSize, 1 do 9 | if TSIL.Rooms.IsGridIndexInRoomShape(i, roomShape) then 10 | return i 11 | end 12 | end 13 | 14 | return 0 15 | end 16 | 17 | 18 | --- Helper function to get the top left wall grid entity. 19 | ---@return GridEntity 20 | function TSIL.GridEntities.GetTopLeftWall() 21 | local room = Game():GetRoom() 22 | 23 | local topLeftGridIndex = TSIL.GridEntities.GetTopLeftWallGridIndex() 24 | 25 | return room:GetGridEntity(topLeftGridIndex) 26 | end -------------------------------------------------------------------------------- /GridEntities/IsGridEntityBreakableByExplosion.lua: -------------------------------------------------------------------------------- 1 | local GridEntityTypeBreakableByExplosion = { 2 | [GridEntityType.GRID_ROCK] = true, 3 | [GridEntityType.GRID_ROCKT] = true, 4 | [GridEntityType.GRID_ROCK_BOMB] = true, 5 | [GridEntityType.GRID_ROCK_ALT] = true, 6 | [GridEntityType.GRID_SPIDERWEB] = true, 7 | [GridEntityType.GRID_TNT] = true, 8 | [GridEntityType.GRID_POOP] = true, 9 | [GridEntityType.GRID_ROCK_SS] = true, 10 | [GridEntityType.GRID_ROCK_SPIKED] = true, 11 | [GridEntityType.GRID_ROCK_ALT2] = true, 12 | [GridEntityType.GRID_ROCK_GOLD] = true, 13 | [GridEntityType.GRID_STATUE] = { 14 | [TSIL.Enums.StatueVariant.ANGEL] = true 15 | } 16 | } 17 | 18 | --- Helper function to check if a GridEntity is able to be broken with an explosion. 19 | ---@param gridEntity GridEntity 20 | ---@return boolean 21 | function TSIL.GridEntities.IsGridEntityBreakableByExplosion(gridEntity) 22 | local gridEntityType = gridEntity:GetType() 23 | local gridEntityVariant = gridEntity:GetVariant() 24 | 25 | local isBreakable = GridEntityTypeBreakableByExplosion[gridEntityType] 26 | 27 | if type(isBreakable) == "table" then 28 | return isBreakable[gridEntityVariant] ~= nil 29 | else 30 | return isBreakable ~= nil 31 | end 32 | end -------------------------------------------------------------------------------- /GridEntities/IsGridEntityBroken.lua: -------------------------------------------------------------------------------- 1 | local GridEntityTypeToBrokenState = { 2 | [GridEntityType.GRID_ROCK] = TSIL.Enums.RockState.BROKEN, 3 | [GridEntityType.GRID_ROCKT] = TSIL.Enums.RockState.BROKEN, 4 | [GridEntityType.GRID_ROCK_BOMB] = TSIL.Enums.RockState.BROKEN, 5 | [GridEntityType.GRID_ROCK_ALT] = TSIL.Enums.RockState.BROKEN, 6 | [GridEntityType.GRID_SPIDERWEB] = TSIL.Enums.SpiderWebState.BROKEN, 7 | [GridEntityType.GRID_LOCK] = TSIL.Enums.LockState.UNLOCKED, 8 | [GridEntityType.GRID_TNT] = TSIL.Enums.TNTState.EXPLODED, 9 | [GridEntityType.GRID_POOP] = TSIL.Enums.PoopState.DESTROYED, 10 | [GridEntityType.GRID_ROCK_SS] = TSIL.Enums.RockState.BROKEN, 11 | [GridEntityType.GRID_ROCK_SPIKED] = TSIL.Enums.RockState.BROKEN, 12 | [GridEntityType.GRID_ROCK_ALT2] = TSIL.Enums.RockState.BROKEN, 13 | [GridEntityType.GRID_ROCK_GOLD] = TSIL.Enums.RockState.BROKEN 14 | } 15 | 16 | --- Helper function to see if the given GridEntity is in its respective broken state. 17 | --- 18 | --- Note that `GridEntityType.GRID_LOCK` will turn to being broken before the actual 19 | --- collision is turned off. 20 | ---@param gridEntity GridEntity 21 | ---@return boolean 22 | function TSIL.GridEntities.IsGridEntityBroken(gridEntity) 23 | local gridEntityType = gridEntity:GetType() 24 | local brokenState = GridEntityTypeToBrokenState[gridEntityType] 25 | 26 | return gridEntity.State == brokenState 27 | end -------------------------------------------------------------------------------- /GridIndexes/GetAllGridIndexes.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get every legal grid index for the current room. 2 | --- If `onlyInRoom` is set to true it will only return those that are actually in the room, 3 | --- accounting for L shaped and small rooms. 4 | ---@param onlyInRoom boolean? @ Default: true 5 | ---@return integer[] 6 | function TSIL.GridIndexes.GetAllGridIndexes(onlyInRoom) 7 | if onlyInRoom == nil then onlyInRoom = true end 8 | 9 | local room = Game():GetRoom() 10 | local gridSize = room:GetGridSize() 11 | local roomShape = room:GetRoomShape() 12 | 13 | local gridIndexes = {} 14 | 15 | for i = 0, gridSize, 1 do 16 | if not onlyInRoom or TSIL.Rooms.IsGridIndexInRoomShape(i, roomShape) then 17 | gridIndexes[#gridIndexes+1] = i 18 | end 19 | end 20 | 21 | return gridIndexes 22 | end -------------------------------------------------------------------------------- /GridIndexes/GetGridIndexesBetween.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get all the grid indexes between two others. 2 | --- 3 | --- Note that the two grid indexes need to be in the same column or row. 4 | ---@param gridIndex1 integer 5 | ---@param gridIndex2 integer 6 | ---@param roomShape RoomShape 7 | ---@return integer[] 8 | function TSIL.GridIndexes.GetGridIndexesBetween(gridIndex1, gridIndex2, roomShape) 9 | if gridIndex1 > gridIndex2 then 10 | local oldGridIndex1 = gridIndex1 11 | local oldGridIndex2 = gridIndex2 12 | gridIndex1 = oldGridIndex2 13 | gridIndex2 = oldGridIndex1 14 | end 15 | 16 | local delta = gridIndex2 - gridIndex1 17 | local gridWidth = TSIL.Rooms.GetRoomShapeGridWidth(roomShape) 18 | 19 | local isOnHorizontalLine = delta <= gridWidth 20 | if isOnHorizontalLine then 21 | local gridIndexes = {} 22 | for i = gridIndex1, gridIndex2, 1 do 23 | gridIndexes[#gridIndexes+1] = i 24 | end 25 | return gridIndexes 26 | end 27 | 28 | local isOnVerticalLine = delta % gridWidth == 0 29 | if isOnVerticalLine then 30 | local gridIndexes = {} 31 | for i = gridIndex1, gridIndex2, gridWidth do 32 | gridIndexes[#gridIndexes+1] = i 33 | end 34 | return gridIndexes 35 | end 36 | 37 | error("Failed to get the grid indexes between " .. gridIndex1 .. " and " .. gridIndex2 .. 38 | " for RoomShape " .. roomShape .. " since they are not on the same horizontal or vertical line.") 39 | end -------------------------------------------------------------------------------- /Input/GetActions.lua: -------------------------------------------------------------------------------- 1 | local MOVE_ACTIONS = { 2 | ButtonAction.ACTION_LEFT, 3 | ButtonAction.ACTION_UP, 4 | ButtonAction.ACTION_RIGHT, 5 | ButtonAction.ACTION_DOWN 6 | } 7 | 8 | local SHOOT_ACTIONS = { 9 | ButtonAction.ACTION_SHOOTLEFT, 10 | ButtonAction.ACTION_SHOOTUP, 11 | ButtonAction.ACTION_SHOOTRIGHT, 12 | ButtonAction.ACTION_SHOOTDOWN 13 | } 14 | 15 | 16 | --- Helper function to get all the values of the `ButtonAction` enum that correspond to movement. 17 | ---@return ButtonAction[] 18 | function TSIL.Input.GetMoveActions() 19 | return TSIL.Utils.Tables.Copy(MOVE_ACTIONS) 20 | end 21 | 22 | 23 | --- Helper function to get all the values of the `ButtonAction` enum that correspond to shooting. 24 | ---@return ButtonAction[] 25 | function TSIL.Input.GetShootActions() 26 | return TSIL.Utils.Tables.Copy(SHOOT_ACTIONS) 27 | end -------------------------------------------------------------------------------- /Input/IsActionPressed.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if a given Button Action is being pressed in any controller. 2 | ---@param action ButtonAction 3 | ---@return boolean 4 | function TSIL.Input.IsActionPressedOnAnyInput(action) 5 | for controllerIndex = 0, 3, 1 do 6 | if Input.IsButtonPressed(action, controllerIndex) then 7 | return true 8 | end 9 | end 10 | 11 | return false 12 | end 13 | 14 | 15 | --- Helper function to check if a given Button Action is being triggered in any controller. 16 | ---@param action ButtonAction 17 | ---@return boolean 18 | function TSIL.Input.IsActionTriggeredOnAnyInput(action) 19 | for controllerIndex = 0, 3, 1 do 20 | if Input.IsButtonTriggered(action, controllerIndex) then 21 | return true 22 | end 23 | end 24 | 25 | return false 26 | end -------------------------------------------------------------------------------- /Input/IsKeyboardPressed.lua: -------------------------------------------------------------------------------- 1 | local MODIFIER_KEYS = { 2 | Keyboard.KEY_LEFT_SHIFT, 3 | Keyboard.KEY_LEFT_CONTROL, 4 | Keyboard.KEY_LEFT_ALT, 5 | Keyboard.KEY_LEFT_SUPER, 6 | Keyboard.KEY_RIGHT_SHIFT, 7 | Keyboard.KEY_RIGHT_CONTROL, 8 | Keyboard.KEY_RIGHT_ALT, 9 | Keyboard.KEY_RIGHT_SUPER, 10 | } 11 | 12 | 13 | --- Helper function to see if any of the given keys are being pressed in the keyboard. 14 | ---@param ... Keyboard 15 | ---@return boolean 16 | function TSIL.Input.IsKeyboardPressed(...) 17 | local keys = {...} 18 | 19 | for _, key in ipairs(keys) do 20 | if Input.IsButtonPressed(key, 0) then 21 | return true 22 | end 23 | end 24 | 25 | return false 26 | end 27 | 28 | 29 | --- Helper function to get the modifier key that is being pressed in the keyboard 30 | --- 31 | --- A modifier key is defined as shift, control, alt, or Windows. 32 | ---@return Keyboard? @ The modifier key that's being pressed, or nil if there are none. 33 | function TSIL.Input.GetPressedModifier() 34 | for _, key in ipairs(MODIFIER_KEYS) do 35 | if Input.IsButtonPressed(key, 0) then 36 | return key 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /Input/MoveActions.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if a given Button Action corresponds to movement. 2 | ---@param buttonAction ButtonAction 3 | ---@return boolean 4 | function TSIL.Input.IsMoveAction(buttonAction) 5 | local moveActions = TSIL.Input.GetMoveActions() 6 | return TSIL.Utils.Tables.IsIn(moveActions, buttonAction) 7 | end 8 | 9 | 10 | --- Helper function to check if a move action is being pressed in any controller. 11 | ---@return boolean 12 | function TSIL.Input.IsMoveActionPressedOnAnyInput() 13 | local moveActions = TSIL.Input.GetMoveActions() 14 | 15 | for _, action in ipairs(moveActions) do 16 | if TSIL.Input.IsActionPressedOnAnyInput(action) then 17 | return true 18 | end 19 | end 20 | 21 | return false 22 | end 23 | 24 | 25 | --- Helper function to check if a move action is being triggered in any controller. 26 | ---@return boolean 27 | function TSIL.Input.IsMoveActionTriggeredOnAnyInput() 28 | local moveActions = TSIL.Input.GetMoveActions() 29 | 30 | for _, action in ipairs(moveActions) do 31 | if TSIL.Input.IsActionTriggeredOnAnyInput(action) then 32 | return true 33 | end 34 | end 35 | 36 | return false 37 | end -------------------------------------------------------------------------------- /Input/ShootActions.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if a given Button Action corresponds to shooting. 2 | ---@param buttonAction ButtonAction 3 | ---@return boolean 4 | function TSIL.Input.IsShootAction(buttonAction) 5 | local shootActions = TSIL.Input.GetShootActions() 6 | return TSIL.Utils.Tables.IsIn(shootActions, buttonAction) 7 | end 8 | 9 | 10 | --- Helper function to check if a shoot action is being pressed in any controller. 11 | ---@return boolean 12 | function TSIL.Input.IsShootActionPressedOnAnyInput() 13 | local shootActions = TSIL.Input.GetShootActions() 14 | 15 | for _, action in ipairs(shootActions) do 16 | if TSIL.Input.IsActionPressedOnAnyInput(action) then 17 | return true 18 | end 19 | end 20 | 21 | return false 22 | end 23 | 24 | 25 | --- Helper function to check if a shoot action is being triggered in any controller. 26 | ---@return boolean 27 | function TSIL.Input.IsShootActionTriggeredOnAnyInput() 28 | local shootActions = TSIL.Input.GetShootActions() 29 | 30 | for _, action in ipairs(shootActions) do 31 | if TSIL.Input.IsActionTriggeredOnAnyInput(action) then 32 | return true 33 | end 34 | end 35 | 36 | return false 37 | end -------------------------------------------------------------------------------- /IsaacAPIClass/GetIsaacAPIClassName.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get the name of a class from the Isaac API. This is contained within the 2 | ---"__type" metatable key. 3 | --- 4 | ---For example, a `Vector` class is has a name of "Vector". 5 | --- 6 | ---Returns nil if the object is not of type `userdata` or if the "__type" metatable key does 7 | ---not exist. 8 | --- 9 | ---In some cases, Isaac classes can be a read-only. If this is the case, the "__type" field will be 10 | ---prepended with "const ". This function will always strip this prefix, if it exists. For example, 11 | ---the class name returned for "const Vector" will be "Vector". 12 | ---@param object unknown 13 | ---@return string? 14 | function TSIL.IsaacAPIClass.GetIsaacAPIClassName(object) 15 | if type(object) ~= "userdata" then 16 | return 17 | end 18 | 19 | local metatable = getmetatable(object) 20 | 21 | if metatable == nil then 22 | return 23 | end 24 | 25 | local classType = metatable.__type 26 | 27 | if type(classType) ~= "string" then 28 | return 29 | end 30 | 31 | local trimmedName = string.gsub(classType, "const ", "") 32 | 33 | return trimmedName 34 | end -------------------------------------------------------------------------------- /Log/GetParentFunctionDescription.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the name and the line number of the current calling function. 2 | --- 3 | --- For this function to work properly, the "--luadebug" flag must be enabled. Otherwise, it will 4 | --- always return undefined. 5 | ---@param levels number? @Default: 3 | The amount of levels to look backwards in the call stack. Default is 3 because the first level is the function, the second level is the calling function, and the third level is the parent of the calling function. 6 | ---@return string? 7 | function TSIL.Log.GetParentFunctionDescription(levels) 8 | levels = levels or 3 9 | 10 | if debug == nil then 11 | return 12 | end 13 | 14 | local debugTable = debug.getinfo(levels) 15 | if debugTable ~= nil then 16 | return debugTable.name .. ":" .. debugTable.linedefined 17 | end 18 | end -------------------------------------------------------------------------------- /Log/Log.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to avoid typing out `Isaac.DebugString()`. 2 | --- 3 | --- If you have the "--luadebug" launch flag turned on, then this 4 | --- function will also prepend the function name and the line number before the string. 5 | ---@param message string 6 | function TSIL.Log.Log(message) 7 | local parentFunctionDescription = TSIL.Log.GetParentFunctionDescription() 8 | 9 | if not parentFunctionDescription then 10 | Isaac.DebugString(message) 11 | else 12 | Isaac.DebugString(parentFunctionDescription .. " - " .. message) 13 | end 14 | end -------------------------------------------------------------------------------- /Pickups/GetCoinValue.lua: -------------------------------------------------------------------------------- 1 | local COIN_SUB_TYPE_TO_VALUE = { 2 | [CoinSubType.COIN_PENNY] = 1, 3 | [CoinSubType.COIN_NICKEL] = 5, 4 | [CoinSubType.COIN_DIME] = 10, 5 | [CoinSubType.COIN_DOUBLEPACK] = 2, 6 | [CoinSubType.COIN_LUCKYPENNY] = 1, 7 | [CoinSubType.COIN_STICKYNICKEL] = 5, 8 | [CoinSubType.COIN_GOLDEN] = 1, 9 | } 10 | 11 | 12 | ---Helper function to get the corresponding coin amount from a `CoinSubType`. Returns 1 for modded sub-types. 13 | ---@param coinSubType CoinSubType 14 | ---@return integer 15 | function TSIL.Pickups.GetCoinValue(coinSubType) 16 | local coinValue = COIN_SUB_TYPE_TO_VALUE[coinSubType] 17 | 18 | if not coinValue then return 1 end 19 | return coinValue 20 | end -------------------------------------------------------------------------------- /Pickups/IsChest.lua: -------------------------------------------------------------------------------- 1 | local CHEST_PICKUP_VARIANTS = { 2 | [PickupVariant.PICKUP_CHEST] = true, 3 | [PickupVariant.PICKUP_BOMBCHEST] = true, 4 | [PickupVariant.PICKUP_SPIKEDCHEST] = true, 5 | [PickupVariant.PICKUP_ETERNALCHEST] = true, 6 | [PickupVariant.PICKUP_MIMICCHEST] = true, 7 | [PickupVariant.PICKUP_OLDCHEST] = true, 8 | [PickupVariant.PICKUP_WOODENCHEST] = true, 9 | [PickupVariant.PICKUP_MEGACHEST] = true, 10 | [PickupVariant.PICKUP_HAUNTEDCHEST] = true, 11 | [PickupVariant.PICKUP_LOCKEDCHEST] = true, 12 | [PickupVariant.PICKUP_REDCHEST] = true, 13 | [PickupVariant.PICKUP_MOMSCHEST] = true, 14 | } 15 | 16 | 17 | ---Helper function to test if the provided pickup matches one of the various chest variants. 18 | ---@param pickup EntityPickup 19 | ---@return boolean 20 | function TSIL.Pickups.IsChest(pickup) 21 | return CHEST_PICKUP_VARIANTS[pickup.Variant] ~= nil 22 | end -------------------------------------------------------------------------------- /Pickups/RedHearts.lua: -------------------------------------------------------------------------------- 1 | local RED_HEART_SUB_TYPES_SET = { 2 | [HeartSubType.HEART_FULL] = true, 3 | [HeartSubType.HEART_HALF] = true, 4 | [HeartSubType.HEART_DOUBLEPACK] = true, 5 | } 6 | 7 | 8 | ---Helper function to get all of the red heart pickup entities in the room. 9 | ---@return EntityPickup[] 10 | function TSIL.Pickups.GetRedHearts() 11 | local hearts = TSIL.PickupSpecific.GetHearts() 12 | 13 | return TSIL.Utils.Tables.Filter(hearts, function (_, heart) 14 | return RED_HEART_SUB_TYPES_SET[heart.SubType] 15 | end) 16 | end 17 | 18 | 19 | ---Helper function to test if the provided pickup matches one of the various red heart sub types. 20 | ---@param pickup EntityPickup 21 | ---@return boolean 22 | function TSIL.Pickups.IsRedHeart(pickup) 23 | return pickup.Variant == PickupVariant.PICKUP_HEART and 24 | RED_HEART_SUB_TYPES_SET[pickup.SubType] ~= nil 25 | end -------------------------------------------------------------------------------- /Pills/HorsePill.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the corresponding horse pill color from a normal pill color 2 | ---@param pillColor PillColor 3 | ---@return PillColor 4 | function TSIL.Pills.GetHorsePillCollor(pillColor) 5 | ---@diagnostic disable-next-line: return-type-mismatch 6 | return pillColor | PillColor.PILL_COLOR_MASK 7 | end 8 | 9 | 10 | --- Helper function to get the corresponding normal pill color from a horse pill color 11 | ---@param pillColor PillColor 12 | ---@return PillColor 13 | function TSIL.Pills.GetNormalPillColorFromHorsePill(pillColor) 14 | ---@diagnostic disable-next-line: return-type-mismatch 15 | return TSIL.Utils.Flags.RemoveFlags(pillColor, PillColor.PILL_COLOR_MASK) 16 | end 17 | 18 | 19 | --- Helper function to check if a pill color corresponds to a horse pill. 20 | ---@param pillColor PillColor 21 | ---@return boolean 22 | function TSIL.Pills.IsHorsePill(pillColor) 23 | return TSIL.Utils.Flags.HasFlags(pillColor, PillColor.PILL_COLOR_MASK) 24 | end -------------------------------------------------------------------------------- /Players/GetDeathAnimationName.lua: -------------------------------------------------------------------------------- 1 | local LOST_STYLE_CHARACTERS_SET = { 2 | [PlayerType.PLAYER_THELOST] = true, -- 10 3 | [PlayerType.PLAYER_THESOUL] = true, -- 17 4 | [PlayerType.PLAYER_THELOST_B] = true, -- 31 5 | [PlayerType.PLAYER_JACOB2_B] = true, -- 39 6 | [PlayerType.PLAYER_THESOUL_B] = true, -- 40 7 | } 8 | 9 | ---Helper function to determine which death animation a character will play 10 | --- - Most characters have a 56 frame death animation (i.e. the "Death" animation). 11 | --- - The Lost and Tainted Lost have a 38 frame death animation (i.e. the "LostDeath" animation). 12 | --- - Tainted Forgotten has a 20 frame death animation (i.e. the "ForgottenDeath" animation). 13 | ---@param character PlayerType 14 | ---@return string 15 | function TSIL.Players.GetCharacterDeathAnimationName(character) 16 | if LOST_STYLE_CHARACTERS_SET[character] then 17 | return "LostDeath" 18 | end 19 | 20 | if character == PlayerType.PLAYER_THEFORGOTTEN_B then 21 | return "ForgottenDeath" 22 | end 23 | 24 | return "Death" 25 | end 26 | -------------------------------------------------------------------------------- /Players/GetNumHitsRemaining.lua: -------------------------------------------------------------------------------- 1 | --- Returns the combined value of all of the player's red hearts, soul/black hearts, and bone hearts, 2 | --- minus the value of the player's rotten hearts. 3 | --- 4 | --- This is equivalent to the number of hits that the player can currently take, but does not take 5 | --- into account double damage from champion enemies and/or being on later floors. (For example, on 6 | --- Womb 1, players who have 1 soul heart remaining would die in 1 hit to anything, even though this 7 | --- function would report that they have 2 hits remaining.) 8 | ---@param player EntityPlayer 9 | ---@return integer 10 | function TSIL.Players.GetPlayerNumHitsRemaining(player) 11 | local hearts = player:GetHearts() 12 | local soulHearts = player:GetSoulHearts(); 13 | local boneHearts = player:GetBoneHearts(); 14 | local eternalHearts = player:GetEternalHearts(); 15 | local rottenHearts = player:GetRottenHearts(); 16 | 17 | return hearts + soulHearts + boneHearts + eternalHearts - rottenHearts; 18 | end -------------------------------------------------------------------------------- /Players/GetPlayerFromEntity.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the player from a tear, laser, bomb, etc. Returns nil if the entity 2 | --- does not correspond to any particular player. 3 | --- 4 | --- This function works by looking at the `Parent` and the `SpawnerEntity` fields (in that order). 5 | --- As a last resort, it will attempt to use the `Entity.ToPlayer` method on the entity itself. 6 | ---@param entity Entity 7 | ---@return EntityPlayer? 8 | function TSIL.Players.GetPlayerFromEntity(entity) 9 | if entity.Parent then 10 | local player = entity.Parent:ToPlayer() 11 | 12 | if player then 13 | return player 14 | end 15 | 16 | local familiar = entity.Parent:ToFamiliar() 17 | 18 | if familiar then 19 | return familiar.Player 20 | end 21 | end 22 | 23 | if entity.SpawnerEntity then 24 | local player = entity.SpawnerEntity:ToPlayer() 25 | 26 | if player then 27 | return player 28 | end 29 | 30 | local familiar = entity.SpawnerEntity:ToFamiliar() 31 | 32 | if familiar then 33 | return familiar.Player 34 | end 35 | end 36 | 37 | return entity:ToPlayer() 38 | end 39 | -------------------------------------------------------------------------------- /Players/Inventory/AnyPlayerHasItem.lua: -------------------------------------------------------------------------------- 1 | --- Returns true if at least one player has the given item. 2 | ---@param collectibleId CollectibleType 3 | ---@param ignoreModifiers boolean? @Default: false 4 | ---@return boolean 5 | function TSIL.Players.DoesAnyPlayerHasItem(collectibleId, ignoreModifiers) 6 | local players = TSIL.Players.GetPlayers() 7 | 8 | local numPlayersWithItem = TSIL.Utils.Tables.Count(players, function (_, player) 9 | return player:HasCollectible(collectibleId, ignoreModifiers) 10 | end) 11 | 12 | return numPlayersWithItem > 0 13 | end -------------------------------------------------------------------------------- /Players/Inventory/AnyPlayerHasTrinket.lua: -------------------------------------------------------------------------------- 1 | --- Returns true if at least one player has the given trinket. 2 | ---@param trinketId TrinketType 3 | ---@param ignoreModifiers boolean? @Default: false 4 | ---@return boolean 5 | function TSIL.Players.DoesAnyPlayerHasTrinket(trinketId, ignoreModifiers) 6 | local players = TSIL.Players.GetPlayers() 7 | 8 | local numPlayersWithTrinket = TSIL.Utils.Tables.Count(players, function (_, player) 9 | return player:HasTrinket(trinketId, ignoreModifiers) 10 | end) 11 | 12 | return numPlayersWithTrinket > 0 13 | end -------------------------------------------------------------------------------- /Players/Inventory/PlayerHasCollectible.lua: -------------------------------------------------------------------------------- 1 | --- Returns `true` if the player has one or more of the provided collectibles 2 | --- 3 | --- This function is variadic, meaning that you can specify as many collectible types that you want 4 | --- to check for. 5 | ---@param player EntityPlayer 6 | ---@vararg CollectibleType 7 | function TSIL.Players.PlayerHasCollectible(player, ...) 8 | return TSIL.Utils.Tables.Some({ ... }, function(collectible) 9 | return player:HasCollectible(collectible) 10 | end) 11 | end 12 | -------------------------------------------------------------------------------- /Players/IsChildPlayer.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to detect if a player is a "child player", meaning they 2 | ---have a defined `Parent` field (i.e. Strawman players) 3 | ---@param player EntityPlayer 4 | function TSIL.Players.IsChildPlayer(player) 5 | return player.Parent ~= nil 6 | end -------------------------------------------------------------------------------- /Players/IsSpecificPlayer.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a player is either Keeper of T.Keeper. 2 | ---@param player EntityPlayer 3 | ---@return boolean 4 | function TSIL.Players.IsKeeper(player) 5 | return player:GetPlayerType() == PlayerType.PLAYER_KEEPER or 6 | player:GetPlayerType() == PlayerType.PLAYER_KEEPER_B 7 | end 8 | 9 | 10 | ---Helper function to check if a player is any form of Tainted Lazarus. 11 | ---@param player EntityPlayer 12 | ---@return boolean 13 | function TSIL.Players.IsTaintedLazarus(player) 14 | return player:GetPlayerType() == PlayerType.PLAYER_LAZARUS2 or 15 | player:GetPlayerType() == PlayerType.PLAYER_LAZARUS2_B 16 | end 17 | 18 | 19 | ---Helper function to check if a player is The Lost or T.The Lost. 20 | ---@param player EntityPlayer 21 | ---@return boolean 22 | function TSIL.Players.IsTheLost(player) 23 | return player:GetPlayerType() == PlayerType.PLAYER_THELOST or 24 | player:GetPlayerType() == PlayerType.PLAYER_THELOST_B 25 | end 26 | 27 | 28 | ---Helper function to check if a player is Bethany or T.Bethany. 29 | ---@param player EntityPlayer 30 | ---@return boolean 31 | function TSIL.Players.IsBethany(player) 32 | return player:GetPlayerType() == PlayerType.PLAYER_BETHANY or 33 | player:GetPlayerType() == PlayerType.PLAYER_BETHANY_B 34 | end 35 | 36 | 37 | ---Helper function to check if a player is either Jacob or Esau. Note that 38 | ---this will only be true for the non tainted versions. 39 | ---@param player EntityPlayer 40 | ---@return boolean 41 | function TSIL.Players.IsJacobOrEsau(player) 42 | return player:GetPlayerType() == PlayerType.PLAYER_JACOB or 43 | player:GetPlayerType() == PlayerType.PLAYER_ESAU 44 | end -------------------------------------------------------------------------------- /Players/PlayerIndex.lua: -------------------------------------------------------------------------------- 1 | ---@class PlayerIndex : integer 2 | 3 | --- Returns a given player's index. Useful for storing unique data per player. 4 | ---@param player EntityPlayer 5 | ---@param differentiateSoulAndForgotten? boolean 6 | ---@return PlayerIndex 7 | function TSIL.Players.GetPlayerIndex(player, differentiateSoulAndForgotten) 8 | if differentiateSoulAndForgotten == nil then 9 | differentiateSoulAndForgotten = false 10 | end 11 | 12 | local playerToUse = player; 13 | local isSubPlayer = player:IsSubPlayer() 14 | if isSubPlayer then 15 | local playerParent = TSIL.Players.GetSubPlayerParent(player) 16 | if playerParent ~= nil then 17 | playerToUse = playerParent 18 | end 19 | end 20 | 21 | if differentiateSoulAndForgotten and player:GetPlayerType() == PlayerType.PLAYER_THESOUL then 22 | ---@diagnostic disable-next-line: return-type-mismatch 23 | return playerToUse:GetCollectibleRNG(3):GetSeed() 24 | end 25 | 26 | ---@diagnostic disable-next-line: return-type-mismatch 27 | return playerToUse:GetCollectibleRNG(1):GetSeed() 28 | end 29 | 30 | --- Returns a player given its index. 31 | ---@param playerIndex PlayerIndex 32 | ---@return EntityPlayer? 33 | function TSIL.Players.GetPlayerByIndex(playerIndex) 34 | local players = TSIL.Players.GetPlayers() 35 | 36 | return TSIL.Utils.Tables.FindFirst(players, function(_, player) 37 | return TSIL.Players.GetPlayerIndex(player) == playerIndex 38 | end) 39 | end 40 | -------------------------------------------------------------------------------- /Players/RemoveCollectibleCostume.lua: -------------------------------------------------------------------------------- 1 | --- Removes a costume from the provided player. 2 | ---@param player EntityPlayer 3 | ---@param collectible CollectibleType 4 | function TSIL.Players.RemoveCollectibleCostume(player, collectible) 5 | local itemConfig = Isaac.GetItemConfig() 6 | local collectibleConfig = itemConfig:GetCollectible(collectible) 7 | 8 | if collectibleConfig then 9 | player:RemoveCostume(collectibleConfig) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Players/TaintedLaz/IsActiveTaintedLaz.lua: -------------------------------------------------------------------------------- 1 | --- Returns wether the given form of tainted lazarus is the active one. 2 | --- If the given player is not tainted lazarus, it'll always return false. 3 | --- 4 | --- Accounts for when the player has Birthright. 5 | ---@param player EntityPlayer 6 | ---@return boolean 7 | function TSIL.Players.IsActiveTaintedLazForm(player) 8 | if player:GetPlayerType() ~= PlayerType.PLAYER_LAZARUS2 and player:GetPlayerType() ~= PlayerType.PLAYER_LAZARUS2_B then return false end 9 | 10 | if player:HasCollectible(CollectibleType.COLLECTIBLE_BIRTHRIGHT) then 11 | local TSILPlayer = TSIL.Players 12 | local playerIndex = TSILPlayer.GetPlayerIndex(player) 13 | local subPlayerIndex = TSILPlayer.GetPlayerIndex(player:GetOtherTwin()) 14 | 15 | for i = 0, Game():GetNumPlayers() - 1, 1 do 16 | local otherPlayer = Game():GetPlayer(i) 17 | local otherPlayerIndex = TSILPlayer.GetPlayerIndex(otherPlayer) 18 | 19 | if otherPlayerIndex == playerIndex then 20 | return true 21 | elseif otherPlayerIndex == subPlayerIndex then 22 | return false 23 | end 24 | end 25 | else 26 | return player:Exists() 27 | end 28 | 29 | return true 30 | end -------------------------------------------------------------------------------- /Players/Trinkets/AddSmeltedTrinket.lua: -------------------------------------------------------------------------------- 1 | --- Gives the player an smelted trinket without changing the player's current held trinkets. 2 | ---@param player EntityPlayer 3 | ---@param trinketId TrinketType 4 | function TSIL.Players.AddSmeltedTrinket(player, trinketId) 5 | local heldTrinket = player:GetTrinket(0) 6 | local heldTrinket2 = player:GetTrinket(1) 7 | 8 | if heldTrinket == 0 then 9 | --The player has no trinkets 10 | player:AddTrinket(trinketId, false) 11 | player:UseActiveItem(CollectibleType.COLLECTIBLE_SMELTER, UseFlag.USE_NOANIM) 12 | else 13 | --The player has at least 1 trinket 14 | if heldTrinket2 ~= 0 then 15 | player:TryRemoveTrinket(heldTrinket2) 16 | end 17 | 18 | player:TryRemoveTrinket(heldTrinket) 19 | 20 | player:AddTrinket(trinketId, false) 21 | player:UseActiveItem(CollectibleType.COLLECTIBLE_SMELTER, UseFlag.USE_NOANIM) 22 | 23 | player:AddTrinket(heldTrinket, false) 24 | 25 | if heldTrinket2 ~= 0 then 26 | player:AddTrinket(heldTrinket2, false) 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /Players/Trinkets/SmeltedTrinketMultiplier.lua: -------------------------------------------------------------------------------- 1 | --- Returns the number of trinkets a player has smelted. 2 | --- Won't count the trinkets they're currently holding. 3 | ---@param player EntityPlayer 4 | ---@param trinket TrinketType 5 | ---@return integer 6 | function TSIL.Players.GetSmeltedTrinketMultiplier(player, trinket) 7 | local totalMultiplier = player:GetTrinketMultiplier(trinket) 8 | local playerHasMomsBox = player:HasCollectible(CollectibleType.COLLECTIBLE_MOMS_BOX) 9 | 10 | for i = 0, 1 do 11 | local slotTrinket = player:GetTrinket(i) 12 | if slotTrinket & ~ TrinketType.TRINKET_GOLDEN_FLAG == trinket then 13 | local reduction = playerHasMomsBox and 2 or 1 14 | if slotTrinket & TrinketType.TRINKET_GOLDEN_FLAG > 0 then 15 | reduction = reduction + 1 16 | end 17 | 18 | totalMultiplier = totalMultiplier - reduction 19 | end 20 | end 21 | 22 | return totalMultiplier 23 | end -------------------------------------------------------------------------------- /RNG/CopyRNG.lua: -------------------------------------------------------------------------------- 1 | ---Copies an `RNG` object 2 | ---@param rng RNG 3 | ---@return RNG 4 | function TSIL.RNG.CopyRNG(rng) 5 | local seed = rng:GetSeed() 6 | return TSIL.RNG.NewRNG(seed) 7 | end 8 | -------------------------------------------------------------------------------- /RNG/GetRandomSeed.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get a random `Seed` value to be used in spawning entities and so on. Use this 2 | ---instead of calling the `Random` function directly since that can return a value of 0 and crash 3 | ---the game. 4 | ---@return integer 5 | function TSIL.RNG.GetRandomSeed() 6 | local randomNumber = Random() 7 | local safeRandomNumber = math.max(1, randomNumber) -- Ensure the seed can never be 0. 8 | return safeRandomNumber 9 | end -------------------------------------------------------------------------------- /RNG/NewRNG.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to initialize an RNG object using Blade's recommended shift index. 2 | ---@param seed integer? The seed to initialize it with. Default is `TSIL.RNG.GetRandomSeed` 3 | ---@return RNG 4 | function TSIL.RNG.NewRNG(seed) 5 | seed = seed or TSIL.RNG.GetRandomSeed() 6 | local rng = RNG() 7 | TSIL.RNG.SetSeed(rng, seed) 8 | return rng 9 | end 10 | -------------------------------------------------------------------------------- /RNG/SetSeed.lua: -------------------------------------------------------------------------------- 1 | local RECOMMENDED_SHIFT_IDX = 35 2 | 3 | --- Helper function to set a seed to an RNG object using Blade's recommended shift index. 4 | ---@param rng RNG 5 | ---@param seed integer 6 | function TSIL.RNG.SetSeed(rng, seed) 7 | assert(seed ~= 0, "You cannot set an RNG object to a seed of 0 or the game will crash.") 8 | rng:SetSeed(seed, RECOMMENDED_SHIFT_IDX) 9 | end -------------------------------------------------------------------------------- /Random/GetRandom.lua: -------------------------------------------------------------------------------- 1 | ---This returns a random float between 0 and 1. It is inclusive on the low end, but exclusive on the 2 | ---high end. (This is because the `RNG.RandomFloat` method will never return a value of exactly 1.) 3 | ---@param seedOrRNG integer | RNG? @Default: `TSIL.RNG.GetRandomSeed()` | The `Seed` or `RNG` object to use. If an `RNG` object is provided, the `RNG.Next` method will be called. 4 | ---@return number 5 | function TSIL.Random.GetRandom(seedOrRNG) 6 | ---@type RNG 7 | local rng 8 | 9 | if TSIL.IsaacAPIClass.IsRNG(seedOrRNG) then 10 | ---@cast seedOrRNG RNG 11 | rng = seedOrRNG 12 | else 13 | ---@cast seedOrRNG number | nil 14 | rng = TSIL.RNG.NewRNG(seedOrRNG) 15 | end 16 | 17 | return rng:RandomFloat() 18 | end -------------------------------------------------------------------------------- /Random/GetRandomFloat.lua: -------------------------------------------------------------------------------- 1 | ---This returns a random float between min and max. 2 | ---@param min number The lower bound for the random number (inclusive). 3 | ---@param max number The upper bound for the random number (exclusive) 4 | ---@param seedOrRNG integer | RNG? @Default: `TSIL.RNG.GetRandomSeed()` | The `Seed` or `RNG` object to use. If an `RNG` object is provided, the `RNG.Next` method will be called. 5 | ---@return number 6 | function TSIL.Random.GetRandomFloat(min, max, seedOrRNG) 7 | if min > max then 8 | local oldMin = min 9 | local oldMax = max 10 | min = oldMax 11 | max = oldMin 12 | end 13 | 14 | return min + TSIL.Random.GetRandom(seedOrRNG) * (max - min) 15 | end -------------------------------------------------------------------------------- /Random/GetRandomInt.lua: -------------------------------------------------------------------------------- 1 | ---This returns a random integer between min and max. It is inclusive on both ends. 2 | ---Note that this function will run the `Next` method on the `RNG` object before returning the 3 | ---random number. 4 | ---@param min integer The lower bound for the random number (inclusive). 5 | ---@param max integer The upper bound for the random number (inclusive) 6 | ---@param seedOrRNG number | RNG? @Default: `TSIL.RNG.GetRandomSeed()` | The `Seed` or `RNG` object to use. If an `RNG` object is provided, the `RNG.Next` method will be called. 7 | ---@param exceptions integer[]? Optional. An array of elements that will be skipped over when getting the random integer. For example, a min of 1, a max of 4, and an exceptions array of `[2]` woudl cause the function to return either 1, 3, or 4. Default is an empty array. 8 | ---@return integer 9 | function TSIL.Random.GetRandomInt(min, max, seedOrRNG, exceptions) 10 | exceptions = exceptions or {} 11 | 12 | ---@type RNG 13 | local rng 14 | 15 | if TSIL.IsaacAPIClass.IsRNG(seedOrRNG) then 16 | ---@cast seedOrRNG RNG 17 | rng = seedOrRNG 18 | else 19 | ---@cast seedOrRNG number | nil 20 | rng = TSIL.RNG.NewRNG(seedOrRNG) 21 | end 22 | 23 | min = math.ceil(min) 24 | max = math.floor(max) 25 | 26 | if min > max then 27 | local oldMin = min 28 | local oldMax = max 29 | min = oldMax 30 | max = oldMin 31 | end 32 | 33 | local exceptionDictionary = TSIL.Utils.Tables.ConstructDictionaryFromTable(exceptions) 34 | local randomInt 35 | 36 | repeat 37 | randomInt = rng:RandomInt(max - min + 1) + min 38 | until not exceptionDictionary[randomInt] 39 | 40 | return randomInt 41 | end -------------------------------------------------------------------------------- /Random/RandomFromTable.lua: -------------------------------------------------------------------------------- 1 | --- Returns n randomly selected elements from a table. 2 | ---@generic T any 3 | ---@param toChoose T[] 4 | ---@param numberOfElements? integer @Default: 1 5 | ---@param seedOrRNG integer | RNG? @Default: `TSIL.RNG.GetRandomSeed()` | The `Seed` or `RNG` object to use. If an `RNG` object is provided, the `RNG.Next` method will be called. 6 | ---@return T[] 7 | function TSIL.Random.GetRandomElementsFromTable(toChoose, numberOfElements, seedOrRNG) 8 | ---@type RNG 9 | local rng 10 | 11 | if TSIL.IsaacAPIClass.IsRNG(seedOrRNG) then 12 | ---@cast seedOrRNG RNG 13 | rng = seedOrRNG 14 | else 15 | ---@cast seedOrRNG number | nil 16 | rng = TSIL.RNG.NewRNG(seedOrRNG) 17 | end 18 | 19 | if numberOfElements == nil then 20 | numberOfElements = 1 21 | end 22 | 23 | local tableSize = TSIL.Utils.Tables.Count(toChoose, function (_, _) 24 | return true 25 | end) 26 | 27 | local leftInTable = tableSize 28 | local leftToChoose = numberOfElements 29 | 30 | local choices = {} 31 | 32 | for index, value in pairs(toChoose) do 33 | if rng:RandomFloat() < leftToChoose/leftInTable then 34 | table.insert(choices, value) 35 | leftToChoose = leftToChoose - 1 36 | end 37 | 38 | leftInTable = leftInTable - 1 39 | end 40 | 41 | return choices 42 | end -------------------------------------------------------------------------------- /Random/RandomFromWeighted.lua: -------------------------------------------------------------------------------- 1 | --- Returns a random value from a weighted list of possibilities. 2 | --- Each choice must be given as a pair of chance and value. 3 | --- 4 | --- `{chance = x, value = y}` 5 | ---@generic T any 6 | ---@param seedOrRNG integer | RNG 7 | ---@param possibles {chance : number, value : T}[] 8 | ---@return T 9 | function TSIL.Random.GetRandomElementFromWeightedList(seedOrRNG, possibles) 10 | ---@type RNG 11 | local rng 12 | 13 | if type(seedOrRNG) == "number" then 14 | rng = RNG() 15 | rng:SetSeed(seedOrRNG, 35) 16 | else 17 | ---@cast seedOrRNG RNG 18 | rng = seedOrRNG 19 | end 20 | 21 | local totalChance = 0 22 | for _, possibility in ipairs(possibles) do 23 | totalChance = totalChance + possibility.chance 24 | end 25 | 26 | local randomChance = TSIL.Random.GetRandomFloat(0, totalChance, rng) 27 | local cumulativeChance = 0 28 | local result = nil 29 | 30 | for _, possibility in ipairs(possibles) do 31 | cumulativeChance = cumulativeChance + possibility.chance 32 | 33 | if cumulativeChance > randomChance then 34 | result = possibility.value 35 | break 36 | end 37 | end 38 | 39 | return result 40 | end -------------------------------------------------------------------------------- /Rooms/GetGridWidth.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to get the width of the grid in a given room shape. 2 | ---@param shape RoomShape 3 | ---@return integer 4 | function TSIL.Rooms.GetRoomShapeGridWidth(shape) 5 | local gridWidth = 15 6 | 7 | if shape == RoomShape.ROOMSHAPE_2x1 or shape == RoomShape.ROOMSHAPE_IIH or 8 | shape == RoomShape.ROOMSHAPE_2x2 or shape == RoomShape.ROOMSHAPE_LTL or 9 | shape == RoomShape.ROOMSHAPE_LTR or shape == RoomShape.ROOMSHAPE_LBL or 10 | shape == RoomShape.ROOMSHAPE_LBR then 11 | gridWidth = 28 12 | end 13 | 14 | return gridWidth 15 | end -------------------------------------------------------------------------------- /Rooms/InBossRoom.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if the current room is a boss room for a particular boss. This will only 2 | --- work for bosses that have dedicated boss rooms in the "00.special rooms.stb" file. 3 | ---@param bossID BossID 4 | ---@return boolean 5 | function TSIL.Rooms.InBossRoomOf(bossID) 6 | local room = Game():GetRoom(); 7 | local roomType = room:GetType(); 8 | local roomStageID = TSIL.Rooms.GetRoomStageID(); 9 | local roomSubType = TSIL.Rooms.GetRoomSubType(); 10 | 11 | return roomType == RoomType.ROOM_BOSS and 12 | roomStageID == TSIL.Enums.StageID.SPECIAL_ROOMS and 13 | roomSubType == bossID 14 | end -------------------------------------------------------------------------------- /Rooms/UpdateRoom.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to trigger a room update without affecting entity positions or velocities. 2 | function TSIL.Rooms.UpdateRoom() 3 | local room = Game():GetRoom() 4 | local entities = TSIL.Entities.GetEntities() 5 | 6 | local positions = TSIL.Entities.GetEntityPositions(entities) 7 | local velocities = TSIL.Entities.GetEntityVelocities(entities) 8 | 9 | room:Update() 10 | 11 | TSIL.Entities.SetEntityPositions(positions, entities) 12 | TSIL.Entities.SetEntityVelocities(velocities, entities) 13 | end -------------------------------------------------------------------------------- /Run/IsGreedMode.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if the current run is a greed run. 2 | ---@return boolean 3 | function TSIL.Run.IsGreedMode() 4 | local difficulty = Game().Difficulty 5 | 6 | return difficulty == Difficulty.DIFFICULTY_GREED or 7 | difficulty == Difficulty.DIFFICULTY_GREEDIER 8 | end -------------------------------------------------------------------------------- /Run/RunUnlockAchievements.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if the current run can unlock achievements. 2 | --- 3 | ---Done by trying to spawn a greed donation machine and checking if it 4 | ---actually spawn. 5 | function TSIL.Run.CanRunUnlockAchievements() 6 | local greedDonation = TSIL.EntitySpecific.SpawnSlot( 7 | TSIL.Enums.SlotVariant.GREED_DONATION_MACHINE, 8 | 0, 9 | Vector.Zero 10 | ) 11 | 12 | local canUnlockAchievements = greedDonation:Exists() 13 | greedDonation:Remove() 14 | 15 | return canUnlockAchievements 16 | end -------------------------------------------------------------------------------- /SaveManager/AddPersistentVariable.lua: -------------------------------------------------------------------------------- 1 | --##use SaveManager/SaveDataManager.lua 2 | 3 | --- Adds a variable to the save manager. 4 | --- The variable name must be unique within your mod. 5 | ---@param mod table 6 | ---@param variableName string 7 | ---@param value any 8 | ---@param persistenceMode VariablePersistenceMode 9 | ---@param ignoreGlowingHourglass? boolean @Default: false 10 | ---@param conditionalSave? fun(): boolean 11 | function TSIL.SaveManager.AddPersistentVariable(mod, variableName, value, persistenceMode, ignoreGlowingHourglass, conditionalSave) 12 | if ignoreGlowingHourglass == nil then 13 | ignoreGlowingHourglass = false 14 | end 15 | 16 | local PersistentData = TSIL.__VERSION_PERSISTENT_DATA.PersistentData 17 | 18 | local modPersistentData = PersistentData[mod.Name] 19 | 20 | if modPersistentData == nil then 21 | ---@type ModPersistentData 22 | modPersistentData = { 23 | mod = mod, 24 | variables = {} 25 | } 26 | PersistentData[mod.Name] = modPersistentData 27 | end 28 | 29 | local modVariables = modPersistentData.variables 30 | 31 | local foundVariable = modVariables[variableName] 32 | 33 | if foundVariable ~= nil then 34 | --The variable already exists 35 | return 36 | end 37 | 38 | ---@type PersistentVariable 39 | local newVariable = { 40 | default = TSIL.Utils.DeepCopy.DeepCopy(value, TSIL.Enums.SerializationType.NONE), 41 | value = value, 42 | persistenceMode = persistenceMode, 43 | ignoreGlowingHourglass = ignoreGlowingHourglass, 44 | conditionalSave = conditionalSave 45 | } 46 | modVariables[variableName] = newVariable 47 | end -------------------------------------------------------------------------------- /SaveManager/GetPersistentVariable.lua: -------------------------------------------------------------------------------- 1 | --##use SaveManager/SaveDataManager.lua 2 | 3 | --- Gets a variable from the save manager. 4 | ---@param mod table 5 | ---@param variableName string 6 | ---@return any 7 | function TSIL.SaveManager.GetPersistentVariable(mod, variableName) 8 | local PersistentData = TSIL.__VERSION_PERSISTENT_DATA.PersistentData 9 | 10 | local modPersistentData = PersistentData[mod.Name] 11 | 12 | if modPersistentData == nil then 13 | --The mod doesn't have any persistent data 14 | return 15 | end 16 | 17 | local modVariables = modPersistentData.variables 18 | 19 | local foundVariable = modVariables[variableName] 20 | 21 | if foundVariable == nil then 22 | --The variable doesn't exists 23 | return 24 | end 25 | 26 | return foundVariable.value 27 | end -------------------------------------------------------------------------------- /SaveManager/RemovePersistentVariable.lua: -------------------------------------------------------------------------------- 1 | --##use SaveManager/SaveDataManager.lua 2 | 3 | --- Removes a variable from the save manager. 4 | ---@param mod table 5 | ---@param variableName string 6 | function TSIL.SaveManager.RemovePersistentVariable(mod, variableName) 7 | local PersistentData = TSIL.__VERSION_PERSISTENT_DATA.PersistentData 8 | 9 | local tables = TSIL.Utils.Tables 10 | 11 | local modPersistentData = PersistentData[mod.Name] 12 | 13 | if modPersistentData == nil then 14 | --The mod doesn't have any persistent data 15 | return 16 | end 17 | 18 | local modVariables = modPersistentData.variables 19 | 20 | modVariables[variableName] = nil 21 | end -------------------------------------------------------------------------------- /SaveManager/ResetPersistentValue.lua: -------------------------------------------------------------------------------- 1 | --##use SaveManager/SaveDataManager.lua 2 | 3 | --- Resets a variable to its default value in the save manager. 4 | ---@param mod table 5 | ---@param variableName string 6 | function TSIL.SaveManager.ResetPersistentVariable(mod, variableName) 7 | local PersistentData = TSIL.__VERSION_PERSISTENT_DATA.PersistentData 8 | 9 | local modPersistentData = PersistentData[mod.Name] 10 | 11 | if modPersistentData == nil then 12 | --The mod doesn't have any persistent data 13 | return 14 | end 15 | 16 | local modVariables = modPersistentData.variables 17 | 18 | local foundVariable = modVariables[variableName] 19 | 20 | if foundVariable == nil then 21 | --The variable doesn't exists 22 | return 23 | end 24 | 25 | foundVariable.value = TSIL.Utils.DeepCopy.DeepCopy(foundVariable.default, TSIL.Enums.SerializationType.NONE) 26 | end -------------------------------------------------------------------------------- /SaveManager/SetPersistentVariable.lua: -------------------------------------------------------------------------------- 1 | --##use SaveManager/SaveDataManager.lua 2 | 3 | --- Sets a variable from the save manager. 4 | ---@param mod table 5 | ---@param variableName string 6 | ---@param newValue any 7 | ---@param overrideType? boolean @default: false 8 | function TSIL.SaveManager.SetPersistentVariable(mod, variableName, newValue, overrideType) 9 | local PersistentData = TSIL.__VERSION_PERSISTENT_DATA.PersistentData 10 | 11 | local tables = TSIL.Utils.Tables 12 | 13 | local modPersistentData = PersistentData[mod.Name] 14 | 15 | if modPersistentData == nil then 16 | --The mod doesn't have any persistent data 17 | return 18 | end 19 | 20 | local modVariables = modPersistentData.variables 21 | 22 | local foundVariable = modVariables[variableName] 23 | 24 | if foundVariable == nil then 25 | --The variable doesn't exist 26 | return 27 | end 28 | 29 | if not overrideType then 30 | local defaultType = type(foundVariable.default) 31 | if defaultType == "userdata" then defaultType = getmetatable(foundVariable.default).__type end 32 | 33 | local newType = type(newValue) 34 | if newType == "userdata" then newType = getmetatable(newValue).__type end 35 | 36 | if defaultType ~= newType then 37 | error("The variable is defined as " .. defaultType .. ". Cannot convert it to " .. newType) 38 | end 39 | end 40 | 41 | foundVariable.value = newValue 42 | end -------------------------------------------------------------------------------- /Shockwaves/IsCustomShockwave.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check whether a given entity is a custom shockwave. 2 | ---@param entity Entity 3 | ---@return boolean 4 | function TSIL.ShockWaves.IsCustomShockwave(entity) 5 | if entity.Type ~= EntityType.ENTITY_EFFECT and 6 | entity.Variant ~= EffectVariant.ROCK_EXPLOSION then return false end 7 | 8 | local customShockwaves = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "TSIL_CUSTOM_SHOCKWAVES") 9 | 10 | if not customShockwaves then return false end 11 | 12 | if customShockwaves[GetPtrHash(entity)] == nil then return false end 13 | 14 | return true 15 | end 16 | 17 | 18 | ---Helper function to get a custom shockwave's data. 19 | ---@param entity Entity 20 | ---@return table? 21 | function TSIL.ShockWaves.GetCustomShockwaveData(entity) 22 | if entity.Type ~= EntityType.ENTITY_EFFECT and 23 | entity.Variant ~= EffectVariant.ROCK_EXPLOSION then return end 24 | 25 | local customShockwaves = TSIL.SaveManager.GetPersistentVariable(TSIL.__MOD, "TSIL_CUSTOM_SHOCKWAVES") 26 | 27 | if not customShockwaves then return end 28 | 29 | return customShockwaves[GetPtrHash(entity)] 30 | end -------------------------------------------------------------------------------- /Sprites/GetLastFrame.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get the last frame of a certain animation. 2 | ---@param sprite Sprite 3 | ---@param animation string? 4 | ---@return integer 5 | function TSIL.Sprites.GetLastFrameOfAnimation(sprite, animation) 6 | local currentAnimation = sprite:GetAnimation() 7 | local currentFrame = sprite:GetFrame() 8 | 9 | if animation ~= nil and animation ~= currentAnimation then 10 | sprite:SetAnimation(animation) 11 | end 12 | sprite:SetLastFrame() 13 | local finalFrame = sprite:GetFrame() 14 | 15 | if animation ~= nil and animation ~= currentAnimation then 16 | sprite:Play(currentAnimation, true) 17 | end 18 | sprite:SetFrame(currentFrame) 19 | 20 | return finalFrame; 21 | end 22 | -------------------------------------------------------------------------------- /Stage/CalculateStageType.lua: -------------------------------------------------------------------------------- 1 | --- Helper function that calculates what the StageType should be for the provided LevelStage in the current run. This 2 | --- emulates what the game's internal code does. 3 | ---@param stage LevelStage 4 | ---@return StageType 5 | function TSIL.Stage.CalculateStageType(stage) 6 | -- The following is the game's internal code to determine the floor type. (This came directly from 7 | -- Spider.) 8 | 9 | --[[ 10 | u32 Seed = g_Game->GetSeeds().GetStageSeed(NextStage); 11 | if (!g_Game->IsGreedMode()) { 12 | StageType = ((Seed % 2) == 0 && ( 13 | ((NextStage == STAGE1_1 || NextStage == STAGE1_2) && gd.Unlocked(ACHIEVEMENT_CELLAR)) || 14 | ((NextStage == STAGE2_1 || NextStage == STAGE2_2) && gd.Unlocked(ACHIEVEMENT_CATACOMBS)) || 15 | ((NextStage == STAGE3_1 || NextStage == STAGE3_2) && gd.Unlocked(ACHIEVEMENT_NECROPOLIS)) || 16 | ((NextStage == STAGE4_1 || NextStage == STAGE4_2))) 17 | ) ? STAGE_TYPE_WOTL : STAGE_TYPE_ORIGINAL; 18 | if (Seed % 3 == 0 && NextStage < STAGE5) 19 | StageType = STAGE_TYPE_AFTERBIRTH; 20 | ]]-- 21 | 22 | local seeds = Game():GetSeeds() 23 | local stageSeed = seeds:GetStageSeed(stage) 24 | 25 | if stageSeed % 2 == 0 then 26 | return StageType.STAGETYPE_WOTL 27 | end 28 | 29 | if stageSeed % 3 == 0 then 30 | return StageType.STAGETYPE_AFTERBIRTH 31 | end 32 | 33 | return StageType.STAGETYPE_ORIGINAL 34 | end -------------------------------------------------------------------------------- /Stage/CalculateStageTypeRepentance.lua: -------------------------------------------------------------------------------- 1 | --- Helper function that calculates what the Repentance StageType should be for the provided LevelStage. 2 | --- This emulates what the game's internal code does. 3 | ---@param stage LevelStage 4 | ---@return StageType 5 | function TSIL.Stage.CalculateStageTypeRepentance(stage) 6 | -- There is no alternate floor for Corpse. 7 | if stage == LevelStage.WOMB_1 or stage == LevelStage.WOMB_2 then 8 | return StageType.STAGETYPE_REPENTANCE 9 | end 10 | 11 | -- This algorithm is from Kilburn. We add one because the alt path is offset by 1 relative to the 12 | -- normal path. 13 | local seeds = Game():GetSeeds() 14 | local adjustedStage = stage + 1 15 | local stageSeed = seeds:GetStageSeed(adjustedStage) 16 | 17 | -- Kilburn does not know why he divided the stage seed by 2 first. 18 | local halfStageSeed = math.floor(stageSeed / 2) 19 | 20 | if halfStageSeed % 2 == 0 then 21 | return StageType.STAGETYPE_REPENTANCE_B 22 | end 23 | 24 | return StageType.STAGETYPE_REPENTANCE 25 | end -------------------------------------------------------------------------------- /Stage/GetEffectiveStage.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to account for Repentance floors being offset by 1. For example, Downpour 2 is 2 | ---the third level of the run, but the game considers it to have a stage of 2. This function will 3 | ---consider Downpour 2 to have a stage of 3. 4 | ---@return integer 5 | function TSIL.Stage.GetEffectiveStage() 6 | local level = Game():GetLevel() 7 | local stage = level:GetStage() 8 | 9 | if TSIL.Stage.OnRepentanceStage() then 10 | return stage + 1 11 | end 12 | 13 | return stage 14 | end -------------------------------------------------------------------------------- /Stage/OnAscent.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if the appropiate ascent flag is set. 2 | ---@return boolean 3 | function TSIL.Stage.OnAscent() 4 | return Game():GetStateFlag(GameStateFlag.STATE_BACKWARDS_PATH) 5 | end -------------------------------------------------------------------------------- /Stage/OnFirstFloor.lua: -------------------------------------------------------------------------------- 1 | ---Returns whether or not the player is on the first floor of the particular run. 2 | --- 3 | ---This is tricky to determine because we have to handle the cases of Downpour/Dross 1 not being the 4 | ---first floor and The Ascent. 5 | ---@return boolean 6 | function TSIL.Stage.OnFirstFloor() 7 | local effectiveStage = TSIL.Stage.GetEffectiveStage() 8 | local isOnAscent = TSIL.Stage.OnAscent() 9 | 10 | return effectiveStage == 1 and not isOnAscent 11 | end -------------------------------------------------------------------------------- /Stage/OnRepentanceStage.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if the current stage type is equal to `StageType.STAGETYPE_REPENTANCE` or `StageType.STAGETYPE_REPENTANCE_B` 2 | ---@return boolean 3 | function TSIL.Stage.OnRepentanceStage() 4 | local level = Game():GetLevel() 5 | local stageType = level:GetStageType() 6 | 7 | return stageType == StageType.STAGETYPE_REPENTANCE or stageType == StageType.STAGETYPE_REPENTANCE_B 8 | end -------------------------------------------------------------------------------- /Trinkets/GoldenTrinket.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a given trinket type corresponds to a golden trinket. 2 | ---@param trinketType TrinketType 3 | ---@return boolean 4 | function TSIL.Trinkets.IsGoldenTrinket(trinketType) 5 | return TSIL.Utils.Flags.HasFlags(trinketType, TrinketType.TRINKET_GOLDEN_FLAG) 6 | end 7 | 8 | 9 | ---Helper function to get the golden trinket type corresponding to a normal trinket type. 10 | ---@param trinketType TrinketType 11 | ---@return integer 12 | function TSIL.Trinkets.GetGoldenTrinketType(trinketType) 13 | return TSIL.Utils.Flags.AddFlags(trinketType, TrinketType.TRINKET_GOLDEN_FLAG) 14 | end 15 | 16 | 17 | ---Helper function to get the normal trinket type corresponding to a golden trinket type. 18 | ---@param trinketType any 19 | ---@return integer 20 | function TSIL.Trinkets.GetNormalTrinketTypeFromGoldenTrinketType(trinketType) 21 | return TSIL.Utils.Flags.RemoveFlags(trinketType, TrinketType.TRINKET_GOLDEN_FLAG) 22 | end -------------------------------------------------------------------------------- /UI/Hearts.lua: -------------------------------------------------------------------------------- 1 | --- Returns how many hearts that are being displayed on the provided player's UI 2 | ---@param player EntityPlayer 3 | ---@return integer 4 | function TSIL.UI.GetVisibleHearts(player) 5 | local effectiveMaxHearts = player:GetEffectiveMaxHearts() 6 | local soulHearts = player:GetSoulHearts() 7 | local boneHearts = player:GetBoneHearts() 8 | local maxHearts = math.max(effectiveMaxHearts, boneHearts * 2) 9 | 10 | local visibleHearts = math.ceil((maxHearts + soulHearts) / 2) 11 | 12 | if visibleHearts < 1 then 13 | visibleHearts = 1 14 | end 15 | 16 | return visibleHearts 17 | end 18 | 19 | --- Returns how many hearts that are being displayed in a row. If the player has more than 6 20 | --- hearts, then this function will return 6 due to the hearts wrapping into rows. 21 | ---@param player EntityPlayer 22 | ---@return integer 23 | function TSIL.UI.GetHeartRowLength(player) 24 | local maxHearts = player:GetMaxHearts() 25 | local soulHearts = player:GetSoulHearts() 26 | local boneHearts = player:GetBoneHearts() 27 | 28 | local combinedHearts = maxHearts + soulHearts + boneHearts 29 | local heartRowLength = combinedHearts / 2 30 | 31 | return math.min(heartRowLength, 6) 32 | end 33 | -------------------------------------------------------------------------------- /UI/ScreenPosition.lua: -------------------------------------------------------------------------------- 1 | ---@return Vector 2 | function TSIL.UI.GetScreenBottomCenterPosition() 3 | local bottomRight = TSIL.UI.GetScreenBottomRightPosition() 4 | return Vector(bottomRight.X / 2, bottomRight.Y) 5 | end 6 | 7 | ---@return Vector 8 | function TSIL.UI.GetScreenBottomLeftPosition() 9 | local bottomRight = TSIL.UI.GetScreenBottomLeftPosition() 10 | return Vector(0, bottomRight.Y) 11 | end 12 | 13 | ---@return Vector 14 | function TSIL.UI.GetScreenBottomRightPosition() 15 | local screenWidth = Isaac.GetScreenWidth() 16 | local screenHeight = Isaac.GetScreenHeight() 17 | 18 | return Vector(screenWidth, screenHeight) 19 | end 20 | 21 | ---@return Vector 22 | function TSIL.UI.GetScreenCenterPosition() 23 | local bottomRight = TSIL.UI.GetScreenBottomRightPosition() 24 | return bottomRight / 2 25 | end 26 | 27 | ---@return Vector 28 | function TSIL.UI.GetScreenTopCenterPosition() 29 | local bottomRight = TSIL.UI.GetScreenBottomRightPosition() 30 | return Vector(bottomRight.X / 2, 0) 31 | end 32 | 33 | ---@return Vector 34 | function TSIL.UI.GetScreenTopLeftPos() 35 | return Vector(0, 0) 36 | end 37 | 38 | ---@return Vector 39 | function TSIL.UI.GetScreenTopRightPos() 40 | local bottomRight = TSIL.UI.GetScreenBottomRightPosition() 41 | return Vector(bottomRight.X, 0) 42 | end 43 | -------------------------------------------------------------------------------- /Utils/Flags/AddFlags.lua: -------------------------------------------------------------------------------- 1 | --- Adds the given flag to another one. 2 | ---@param flags integer 3 | ---@param ... integer 4 | ---@return integer 5 | function TSIL.Utils.Flags.AddFlags(flags, ...) 6 | local flagsToAdd = {...} 7 | 8 | for _, flag in ipairs(flagsToAdd) do 9 | flags = flags | flag 10 | end 11 | 12 | return flags 13 | end -------------------------------------------------------------------------------- /Utils/Flags/CountBits.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to count all the set bits in a mask 2 | ---@param mask integer 3 | ---@return integer 4 | function TSIL.Utils.Flags.CountBits(mask) 5 | local total = 0 6 | 7 | while mask ~= 0 do 8 | total = total + 1 9 | mask = mask & mask - 1 10 | end 11 | 12 | return total 13 | end -------------------------------------------------------------------------------- /Utils/Flags/HasFlags.lua: -------------------------------------------------------------------------------- 1 | --- Checks whether a given flag has all of the other given flags. 2 | ---@param flags integer 3 | ---@param ... integer 4 | ---@return boolean 5 | function TSIL.Utils.Flags.HasFlags(flags, ...) 6 | local flagsToCheck = {...} 7 | 8 | for _, flag in ipairs(flagsToCheck) do 9 | if flags & flag ~= flag then 10 | return false 11 | end 12 | end 13 | 14 | return true 15 | end -------------------------------------------------------------------------------- /Utils/Flags/RemoveFlags.lua: -------------------------------------------------------------------------------- 1 | --- Removes the given flags from another one. 2 | ---@param flags integer 3 | ---@param ... integer 4 | ---@return integer 5 | function TSIL.Utils.Flags.RemoveFlags(flags, ...) 6 | local flagsToRemove = {...} 7 | 8 | for _, flag in ipairs(flagsToRemove) do 9 | flags = flags & ~flag 10 | end 11 | 12 | return flags 13 | end -------------------------------------------------------------------------------- /Utils/Functions/RunNextCallback.lua: -------------------------------------------------------------------------------- 1 | --- Runs a function the next time a callback is triggered. After it gets called once, it is removed 2 | ---@param mod table 3 | ---@param callback ModCallbacks | CustomCallback 4 | ---@param funct function 5 | ---@param optionalParam any? 6 | function TSIL.Utils.Functions.RunNextCallback(mod, callback, funct, optionalParam) 7 | local callbackFunct 8 | callbackFunct = function (...) 9 | mod:RemoveCallback(callback, callbackFunct) 10 | return funct(...) 11 | end 12 | 13 | mod:AddCallback( 14 | callback, 15 | callbackFunct, 16 | optionalParam 17 | ) 18 | end -------------------------------------------------------------------------------- /Utils/Functions/RunNextLevel.lua: -------------------------------------------------------------------------------- 1 | local FunctionsToRun = {} 2 | 3 | local function OnNewLevel() 4 | TSIL.Utils.Tables.ForEach(FunctionsToRun, function (_, functionToRun) 5 | functionToRun.funct(table.unpack(functionToRun.params)) 6 | end) 7 | 8 | FunctionsToRun = {} 9 | end 10 | TSIL.__AddInternalCallback( 11 | "RUN_NEXT_LEVEL_POST_NEW_LEVEL", 12 | ModCallbacks.MC_POST_NEW_LEVEL, 13 | OnNewLevel 14 | ) 15 | 16 | --- Runs a given function on the next `POST_NEW_LEVEL` callback. 17 | ---@param funct function 18 | ---@param ... any 19 | function TSIL.Utils.Functions.RunNextLevel(funct, ...) 20 | local args = {...} 21 | 22 | table.insert(FunctionsToRun, {funct = funct, params = args}) 23 | end -------------------------------------------------------------------------------- /Utils/Functions/RunNextRoom.lua: -------------------------------------------------------------------------------- 1 | local FunctionsToRun = {} 2 | 3 | local function OnNewRoom() 4 | TSIL.Utils.Tables.ForEach(FunctionsToRun, function (_, functionToRun) 5 | functionToRun.funct(table.unpack(functionToRun.params)) 6 | end) 7 | 8 | FunctionsToRun = {} 9 | end 10 | TSIL.__AddInternalCallback( 11 | "RUN_NEXT_ROOM_POST_NEW_ROOM", 12 | ModCallbacks.MC_POST_NEW_ROOM, 13 | OnNewRoom 14 | ) 15 | 16 | 17 | --- Runs a given function on the next `POST_NEW_ROOM` callback. 18 | ---@param funct function 19 | ---@param ... any 20 | function TSIL.Utils.Functions.RunNextRoom(funct, ...) 21 | local args = {...} 22 | 23 | table.insert(FunctionsToRun, {funct = funct, params = args}) 24 | end -------------------------------------------------------------------------------- /Utils/Math/CircleIntersectingRectangle.lua: -------------------------------------------------------------------------------- 1 | --- Returns whether a given rectangle is intersecting a given circle. 2 | ---@param RectPos Vector @Center of the rectangle 3 | ---@param RectSize Vector 4 | ---@param CirclePos Vector @Center of the circle 5 | ---@param CircleSize number 6 | ---@return boolean 7 | function TSIL.Utils.Math.IsCircleIntersectingWithRectangle(RectPos, RectSize, CirclePos, CircleSize) 8 | local circleDistanceX = math.abs(CirclePos.X - RectPos.X) 9 | local circleDistanceY = math.abs(CirclePos.Y - RectPos.Y) 10 | 11 | if circleDistanceX > RectSize.X/2 + CircleSize + 0.1 or 12 | circleDistanceY > RectSize.Y/2 + CircleSize + 0.1 then 13 | return false 14 | elseif circleDistanceX <= 20 or circleDistanceY <= 20 then 15 | return true 16 | else 17 | local cornerDistanceSq = (circleDistanceX - 20)^2 + (circleDistanceY - 20)^2 18 | return cornerDistanceSq <= (CircleSize + 0.1)^2 19 | end 20 | end -------------------------------------------------------------------------------- /Utils/Math/Clamp.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to clamp a number into a range. 2 | ---@param a number 3 | ---@param min number 4 | ---@param max number 5 | ---@return number 6 | function TSIL.Utils.Math.Clamp(a, min, max) 7 | if min > max then 8 | local temp = min 9 | min = max 10 | max = temp 11 | end 12 | 13 | return math.max(min, math.min(a, max)) 14 | end -------------------------------------------------------------------------------- /Utils/Math/IsInRectangle.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a point is inside a rectangle. 2 | --- 3 | ---Also works with rotated rectangles. 4 | ---@param pos Vector 5 | ---@param topLeft Vector 6 | ---@param topRight Vector 7 | ---@param bottomRight Vector 8 | ---@return boolean 9 | function TSIL.Utils.Math.IsInRectangle(pos, topLeft, topRight, bottomRight) 10 | local AM = pos - topRight 11 | local AB = bottomRight - topRight 12 | local AD = topLeft - topRight 13 | 14 | local AMpAB = TSIL.Utils.Math.ScalarProduct(AM, AB) 15 | local ABpAB = TSIL.Utils.Math.ScalarProduct(AB, AB) 16 | local AMpAD = TSIL.Utils.Math.ScalarProduct(AM, AD) 17 | local ADpAD = TSIL.Utils.Math.ScalarProduct(AD, AD) 18 | 19 | return 0 < AMpAB and AMpAB < ABpAB and 0 < AMpAD and AMpAD < ADpAD 20 | end -------------------------------------------------------------------------------- /Utils/Math/Lerp.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to lineally interpolate between two numbers. 2 | ---@param a number 3 | ---@param b number 4 | ---@param t number 5 | ---@return number 6 | function TSIL.Utils.Math.Lerp(a, b, t) 7 | return a + (b - a) * t; 8 | end -------------------------------------------------------------------------------- /Utils/Math/Round.lua: -------------------------------------------------------------------------------- 1 | --- Rounds a number to the closest number of decimal places given. 2 | --- 3 | --- Defaults to rounding to the nearest integer. 4 | ---@param n number 5 | ---@param decimalPlaces integer? @Default: 0 6 | ---@return number 7 | function TSIL.Utils.Math.Round(n, decimalPlaces) 8 | decimalPlaces = decimalPlaces or 0 9 | local mult = 10^(decimalPlaces or 0) 10 | return math.floor(n * mult + 0.5) / mult 11 | end -------------------------------------------------------------------------------- /Utils/Math/ScalarProduct.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to get the scalar product of 2 vectors. 2 | --- 3 | ---The scalar product is defined as `v1.X * v2.X + v1.Y * v2.Y` 4 | ---@param v1 Vector 5 | ---@param v2 Vector 6 | ---@return number 7 | function TSIL.Utils.Math.ScalarProduct(v1, v2) 8 | return v1.X * v2.X + v1.Y * v2.Y 9 | end -------------------------------------------------------------------------------- /Utils/String/EndsWith.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a string ends with a given suffix. 2 | ---@param s string 3 | ---@param suffix string 4 | ---@return boolean 5 | function TSIL.Utils.String.EndsWith(s, suffix) 6 | return string.sub(s, (#s - #suffix) + 1) == suffix 7 | end -------------------------------------------------------------------------------- /Utils/String/Split.lua: -------------------------------------------------------------------------------- 1 | ---Helper function that splits a string into parts based on the given separator. 2 | --- 3 | ---For example, splitting the string "Hello there, world!" with separator " " will return ["Hello", "there,", "world!"] 4 | ---@param s string 5 | ---@param separator string 6 | ---@return string[] 7 | function TSIL.Utils.String.Split(s, separator) 8 | local result = {} 9 | for substring in string.gmatch(s, "([^" .. separator .. "]+)") do 10 | table.insert(result, substring) 11 | end 12 | 13 | return result 14 | end -------------------------------------------------------------------------------- /Utils/String/StartsWith.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a string starts with a given prefix. 2 | ---@param s string 3 | ---@param prefix string 4 | ---@return boolean 5 | function TSIL.Utils.String.StartsWith(s, prefix) 6 | return s:sub(1, #prefix) == prefix 7 | end -------------------------------------------------------------------------------- /Utils/String/TrimPrefix.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to remove a given prefix from a string. 2 | ---@param s string 3 | ---@param prefix string 4 | ---@return string 5 | function TSIL.Utils.String.TrimPrefix(s, prefix) 6 | if s:sub(1, #prefix) ~= prefix then 7 | return s 8 | end 9 | 10 | return s:sub(#prefix + 1) 11 | end -------------------------------------------------------------------------------- /Utils/Tables/AllMatch.lua: -------------------------------------------------------------------------------- 1 | --- Helper function to check if all the elements of a table match a given predicate. 2 | ---@generic T:any 3 | ---@param toCheck T[] 4 | ---@param predicate fun(key: integer | string, value: T): boolean 5 | ---@return boolean 6 | function TSIL.Utils.Tables.All(toCheck, predicate) 7 | for index, value in pairs(toCheck) do 8 | if not predicate(index, value) then 9 | return false 10 | end 11 | end 12 | 13 | return true 14 | end -------------------------------------------------------------------------------- /Utils/Tables/ConstructDictionaryFromTable.lua: -------------------------------------------------------------------------------- 1 | ---Constructs a dictionary from a table. Note that the value of each key is set to true. 2 | ---@generic T 3 | ---@param oldTable T[] 4 | ---@return table 5 | function TSIL.Utils.Tables.ConstructDictionaryFromTable(oldTable) 6 | local dictionary = {} 7 | 8 | for _, v in pairs(oldTable) do 9 | dictionary[v] = true 10 | end 11 | 12 | return dictionary 13 | end 14 | -------------------------------------------------------------------------------- /Utils/Tables/Copy.lua: -------------------------------------------------------------------------------- 1 | --- Returns a safe copy of a table. 2 | --- 3 | --- It copies the tables inside it recursively. 4 | ---@param toCopy table 5 | ---@return table 6 | function TSIL.Utils.Tables.Copy(toCopy) 7 | local copy = {} 8 | for index, value in pairs(toCopy) do 9 | if type(value) == "table" then 10 | copy[index] = TSIL.Utils.Tables.Copy(value) 11 | else 12 | copy[index] = value 13 | end 14 | end 15 | 16 | return copy 17 | end -------------------------------------------------------------------------------- /Utils/Tables/CopyUserdataValuesToTable.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to copy specific values from a userdata to a table. 2 | ---@param object unknown 3 | ---@param keys string[] 4 | ---@param map table 5 | function TSIL.Utils.Tables.CopyUserdataValuesToTable(object, keys, map) 6 | assert(type(object) == "userdata", "Failed to copy an object values to a table, since the object was of type " .. type(object)) 7 | 8 | for _, key in pairs(keys) do 9 | local value = object[key] 10 | map[key] = value 11 | end 12 | end -------------------------------------------------------------------------------- /Utils/Tables/Count.lua: -------------------------------------------------------------------------------- 1 | --- Counts how many elements are on a given table that match a predicate. 2 | --- 3 | --- If no predicate is given, it'll count all the elements. 4 | ---@generic T:any 5 | ---@param toCount T[] 6 | ---@param predicate fun(key: integer | string, value: T): boolean @Default: foo() -> true 7 | ---@return integer 8 | function TSIL.Utils.Tables.Count(toCount, predicate) 9 | if predicate == nil then 10 | predicate = function() return true end 11 | end 12 | 13 | local filtered = TSIL.Utils.Tables.Filter(toCount, predicate) 14 | 15 | local count = 0 16 | for _, _ in pairs(filtered) do 17 | count = count + 1 18 | end 19 | 20 | return count 21 | end -------------------------------------------------------------------------------- /Utils/Tables/Equals.lua: -------------------------------------------------------------------------------- 1 | --- Helper function for determining if two arrays contain the exact same elements. 2 | ---@generic T: any 3 | ---@param table1 T[] 4 | ---@param table2 T[] 5 | ---@return boolean 6 | function TSIL.Utils.Tables.Equals(table1, table2) 7 | if #table1 ~= #table2 then 8 | return false 9 | end 10 | 11 | for i, v in pairs(table1) do 12 | local table2Element = table2[i] 13 | 14 | if type(table2Element) == "table" and type(v) == "table" then 15 | local isTableMatching = TSIL.Utils.Tables.Equals(table2Element, v) 16 | if not isTableMatching then 17 | return false 18 | end 19 | elseif table2Element ~= v then 20 | return false 21 | end 22 | end 23 | 24 | return true 25 | end 26 | 27 | -------------------------------------------------------------------------------- /Utils/Tables/Filter.lua: -------------------------------------------------------------------------------- 1 | --- Filters a table given a predicate 2 | ---@generic T:any 3 | ---@param toFilter T[] 4 | ---@param predicate fun(key: integer | string, value: T): boolean 5 | ---@return T[] 6 | function TSIL.Utils.Tables.Filter(toFilter, predicate) 7 | local filtered = {} 8 | 9 | for index, value in pairs(toFilter) do 10 | if predicate(index, value) then 11 | filtered[#filtered+1] = value 12 | end 13 | end 14 | 15 | return filtered 16 | end -------------------------------------------------------------------------------- /Utils/Tables/FindFirst.lua: -------------------------------------------------------------------------------- 1 | --- Returns the first value of a table that matches a given predicate. 2 | --- 3 | --- If it doesn't find any, it returns nil. 4 | ---@generic T : any 5 | ---@param toFind T[] 6 | ---@param predicate fun(key: integer | string, value: T): boolean 7 | ---@return T? 8 | function TSIL.Utils.Tables.FindFirst(toFind, predicate) 9 | for index, value in pairs(toFind) do 10 | if predicate(index, value) then 11 | return value 12 | end 13 | end 14 | 15 | return nil 16 | end -------------------------------------------------------------------------------- /Utils/Tables/ForEach.lua: -------------------------------------------------------------------------------- 1 | --- Executes a function for each key-value pair of a table 2 | ---@generic T:any 3 | ---@param toIterate T[] 4 | ---@param funct fun(index: string|integer, value:T) 5 | function TSIL.Utils.Tables.ForEach(toIterate, funct) 6 | for index, value in pairs(toIterate) do 7 | funct(index, value) 8 | end 9 | end -------------------------------------------------------------------------------- /Utils/Tables/GetDictionaryKeys.lua: -------------------------------------------------------------------------------- 1 | ---Returns a list of keys a dictionary has. 2 | ---@generic K 3 | ---@generic V 4 | ---@param dictionary table 5 | ---@return K[] 6 | function TSIL.Utils.Tables.GetDictionaryKeys(dictionary) 7 | local result = {} 8 | for i in pairs(dictionary) do 9 | table.insert(result, i) 10 | end 11 | return result 12 | end -------------------------------------------------------------------------------- /Utils/Tables/GetNumbersFromTable.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to safely get number values from specific keys on a Lua table. Will throw an 2 | ---error if the specific value does not exist on the table or if it cannot be converted to a number. 3 | --- 4 | ---This function is variadic, meaning that you can specify N arguments to get N values. 5 | ---@param map table 6 | ---@param objectName string 7 | ---@vararg string 8 | ---@return number[] 9 | function TSIL.Utils.Tables.GetNumbersFromTable(map, objectName, ...) 10 | local keys = {...} 11 | local numbers = {} 12 | 13 | for _, v in pairs(keys) do 14 | local value = map[v] 15 | 16 | if value == nil then 17 | error("Failed to find a value for " .. v .. " in a table representing a " .. objectName .. " object") 18 | end 19 | 20 | if type(value) == "number" then 21 | table.insert(numbers, value) 22 | elseif type(value) == "string" then 23 | local number = tonumber(value) 24 | if number == nil then 25 | error("Failed to convert the " .. v .. " value of a table representing a " .. objectName .. " object to a number: " .. value) 26 | else 27 | table.insert(numbers, number) 28 | end 29 | else 30 | error("Failed to get the number for the " .. v .. " value of a table representing a " .. objectName .. " object because the type was " .. type(value)) 31 | end 32 | end 33 | 34 | return numbers 35 | end -------------------------------------------------------------------------------- /Utils/Tables/HasNumberKeys.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if the given object is a table with non 2 | ---consecutive number keys. 3 | --- 4 | ---This is useful since this kind of table doesn't get properly serialized 5 | ---by the save manager. 6 | ---@param object unknown 7 | ---@return boolean 8 | function TSIL.Utils.Tables.HasNonConsecutiveNumberKeys(object) 9 | if type(object) ~= "table" then 10 | return false 11 | end 12 | 13 | local expectedTableKey = 1 14 | local hasAllExpectedKeys = true 15 | 16 | for key, _ in pairs(object) do 17 | if type(key) == "string" then 18 | return false 19 | end 20 | 21 | if key ~= expectedTableKey then 22 | hasAllExpectedKeys = false 23 | break 24 | end 25 | 26 | expectedTableKey = expectedTableKey + 1 27 | end 28 | 29 | if hasAllExpectedKeys then 30 | return false 31 | end 32 | 33 | return true 34 | end -------------------------------------------------------------------------------- /Utils/Tables/IsArray.lua: -------------------------------------------------------------------------------- 1 | --- Returns if the provided table is an array. This is a workaround as Lua has no formal way to differentiate between a normal array and a map. 2 | ---@param object unknown 3 | ---@return boolean 4 | function TSIL.Utils.Tables.IsArray(object) 5 | if type(object) ~= "table" then 6 | return false 7 | end 8 | 9 | if next(object) == nil then 10 | return true 11 | end 12 | 13 | local isArray = true 14 | 15 | for i in next, object do 16 | if type(i) ~= "number" or i%1 ~= 0 or i <= 0 then 17 | isArray = false 18 | end 19 | end 20 | 21 | return isArray 22 | end -------------------------------------------------------------------------------- /Utils/Tables/IsEmpty.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a table is empty. Use this instead of checking for 2 | ---a size of 0 with the `#` operator since this will take into account dictionaries. 3 | ---@param map table 4 | ---@return boolean 5 | function TSIL.Utils.Tables.IsEmpty(map) 6 | return next(map) == nil 7 | end 8 | -------------------------------------------------------------------------------- /Utils/Tables/IsInTable.lua: -------------------------------------------------------------------------------- 1 | --- Returns whether a given element is on a table 2 | ---@generic T:any 3 | ---@param list T[] 4 | ---@param element T 5 | ---@return boolean 6 | function TSIL.Utils.Tables.IsIn(list, element) 7 | local found = TSIL.Utils.Tables.FindFirst(list, function (_, value) 8 | if type(value) == "table" and type(element) == "table" then 9 | return TSIL.Utils.Tables.Equals(value, element) 10 | else 11 | return element == value 12 | end 13 | end) 14 | 15 | return found ~= nil 16 | end -------------------------------------------------------------------------------- /Utils/Tables/Map.lua: -------------------------------------------------------------------------------- 1 | --- Creates a new table where each element is the result of applying 2 | --- a given function to each element of the provided list. 3 | ---@generic T any 4 | ---@generic S any 5 | ---@param list T[] 6 | ---@param funct fun(index: string|integer, value:T) : S 7 | ---@return S[] 8 | function TSIL.Utils.Tables.Map(list, funct) 9 | local mapped = {} 10 | 11 | for key, value in pairs(list) do 12 | mapped[key] = funct(key, value) 13 | end 14 | 15 | return mapped 16 | end -------------------------------------------------------------------------------- /Utils/Tables/SomeMatch.lua: -------------------------------------------------------------------------------- 1 | --- Runs the provided callback for the provided table. Returns true if the callback returns true for 2 | --- at least one element in the table. 3 | ---@generic K 4 | ---@generic V 5 | ---@param tbl table 6 | ---@param predicate fun(value: V, index: K, tbl: table): boolean 7 | ---@return boolean 8 | function TSIL.Utils.Tables.Some(tbl, predicate) 9 | for k, v in pairs(tbl) do 10 | if predicate(v, k, tbl) then 11 | return true 12 | end 13 | end 14 | 15 | return false 16 | end 17 | -------------------------------------------------------------------------------- /Utils/Tables/TableHasKeys.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if a Lua table has all of the provided keys. 2 | ---This function is variadic, meaning that you can specify as many arguments as you want to check for. 3 | ---@param map table 4 | ---@vararg string 5 | function TSIL.Utils.Tables.TableHasKeys(map, ...) 6 | local keys = {...} 7 | for _, v in pairs(keys) do 8 | if map[v] == nil then 9 | return false 10 | end 11 | end 12 | 13 | return true 14 | end -------------------------------------------------------------------------------- /Vector/CopyVector.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to copy a `Vector` Isaac API class. 2 | ---@param vector Vector 3 | ---@return Vector 4 | function TSIL.Vector.CopyVector(vector) 5 | if not TSIL.IsaacAPIClass.IsVector(vector) then 6 | error("Failed to copy a Vector object since the provided object was not a userdata Vector class.") 7 | end 8 | 9 | return Vector(vector.X, vector.Y) 10 | end -------------------------------------------------------------------------------- /Vector/Equals.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to check if two vectors are exactly equal. 2 | ---@param v1 Vector 3 | ---@param v2 Vector 4 | ---@return boolean 5 | function TSIL.Vector.VectorEquals(v1, v2) 6 | return v1.X == v2.X and v1.Y == v1.Y 7 | end -------------------------------------------------------------------------------- /Vector/FuzzyEq.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to see if two vectors are equal within the given tolerance `epsilon`. 2 | ---@param v1 Vector 3 | ---@param v2 Vector 4 | ---@param epsilon number? @Default: 0.001 | The tolerance to use when comparing the vectors. 5 | ---@return boolean 6 | function TSIL.Vector.VectorFuzzyEquals(v1, v2, epsilon) 7 | epsilon = epsilon or 1e-3 8 | return math.abs(v1.X - v2.X) < epsilon and math.abs(v1.Y - v2.Y) < epsilon 9 | end -------------------------------------------------------------------------------- /Vector/GetRandomVector.lua: -------------------------------------------------------------------------------- 1 | --- Returns a random vector between (-1, -1) and (1, 1). You can get a larger vector by multiplying 2 | --- the returned vector. 3 | --- 4 | --- Unlike `RandomVector()`, this function supports seeding. 5 | ---@param seedOrRNG? integer | RNG Optional. The seed or `RNG` object to use. If an `RNG` object is provided, the `RNG:Next` method will be called. Default is `GetRandomSeed` 6 | function TSIL.Vector.GetRandomVector(seedOrRNG) 7 | local rng 8 | 9 | if TSIL.IsaacAPIClass.IsRNG(seedOrRNG) then 10 | rng = seedOrRNG 11 | else 12 | ---@diagnostic disable-next-line: param-type-mismatch 13 | rng = TSIL.RNG.NewRNG(seedOrRNG) 14 | end 15 | 16 | local x = TSIL.Random.GetRandom(rng) 17 | local y = TSIL.Random.GetRandom(rng) 18 | 19 | return Vector(x, y) 20 | end 21 | -------------------------------------------------------------------------------- /Vector/HasLength.lua: -------------------------------------------------------------------------------- 1 | ---Helper function to see if a vector has a length greater than zero within the given tolerance `epsilon`. 2 | ---@param v Vector 3 | ---@param epsilon number? @Default: 0.001 | The tolerance of how close to zero the vector can be. 4 | ---@return boolean 5 | function TSIL.Vector.VectorHasLength(v, epsilon) 6 | epsilon = epsilon or 1e-3 7 | return v:Length() > epsilon 8 | end -------------------------------------------------------------------------------- /Vector/VectorToDirection.lua: -------------------------------------------------------------------------------- 1 | --- Returns a direction corresponding to the direction the provided vector is pointing. 2 | ---@param vector Vector 3 | ---@return Direction 4 | function TSIL.Vector.VectorToDirection(vector) 5 | local angle = vector:GetAngleDegrees() 6 | return TSIL.Direction.AngleToDirection(angle) 7 | end 8 | --------------------------------------------------------------------------------