This document is primarily for modders who want to incorporate their characters/items/etc into our mod.
If you're looking for information about characters/items/etc in this mod, visit the official Epiphany wiki.

Epiphany API Documentation

Updated 2nd of Feb '23

The mod can be accessed through the Epiphany global. While a large number of tables/functions in the mod can already be accessed from outside, we provide an API which contains functions that help with performing some common actions. The API can be accessed through Epiphany.API.

A sample character that functions as a practical example on how to use the Epiphany API can be downloaded from here: https://gitlab.com/OpenSauce04/epiphany-api-sample

Adding a character

Adding an existing character to the tarnished character selection wheel is done through AddCharacter function:

function Epiphany.API.AddCharacter(charInfo: table)

This function specifically should always be run under a MC_POST_NEW_LEVEL callback. This is so that reguardless of load order, the character will be added after Epiphany is loaded, but before the character menu's first MC_POST_GAME_STARTED callback. Within this function, the callback should be removed using RemoveCallback(CallbackID, Function), as it does not need to be run again.

charInfo is a table that contains all required info about a character. It MUST contain the following fields:

Additionally, you may optionally include the following fields:

Example code

This is an example of adding a character through the API taken from the code of the Epiphany API sample character:

-- CHARACTER STATS
local stats_opensauce={
  HEALTH_RED = 2,
  HEALTH_ROTTEN = 1,
  HEALTH_SOUL = 4
}

local OpenSauceID = Isaac.GetPlayerTypeByName("​OpenSauce")

-- INITIALIZE EPIPHANY MENU CHARACTER
function Mod:AddOpenSauceCharacter()
  -- DO NOT RUN IF THE API IS NOT LOADED
  if not Epiphany or not Epiphany.API then return end

  -- ADD THE ACTUAL CHARACTER
  Epiphany.API.AddCharacter({
    charName = "OPENSAUCE", --Internal character name (REQUIRED)
    charID = OpenSauceID, -- Character ID (REQUIRED)
    charStats = stats_opensauce, -- Stat array
    costume = "gfx/characters/character_opensauce.anm2", -- Main costume (REQUIRED)
    extraCostume = {Isaac.GetCostumeIdByPath("gfx/characters/character_opensauce_extra.anm2")}, -- Extra costume (e.g. Maggy's Hair)
    menuGraphics = "gfx/ui/menu_opensauce.anm2", -- Character menu graphics (portrait, text) (REQUIRED)
    coopMenuSprite = "gfx/ui/Coop/coop_menu_opensauce.anm2", -- Co-op menu icon (REQUIRED)
    pocketItem = CollectibleType.COLLECTIBLE_SULFUR, -- Pocket active
    pocketItemPersistent = true, -- Should the pocket active always be re-given when not present? (false is vanilla behaviour)
    unlockChecker = function() return true end, -- function that returns whether the character is unlocked. Defaults to always returning true.
    floorTutorial = "gfx/grid/tutorial_opensauce.anm2"
  })
  Mod:RemoveCallback(ModCallbacks.MC_POST_NEW_LEVEL, Mod.AddOpenSauceCharacter)
end

Mod:AddCallback(ModCallbacks.MC_POST_NEW_LEVEL, Mod.AddOpenSauceCharacter)

Adding Throwing Bag synergies

The Throwing Bag item was made with the ability to easily add item synergies in mind, so creating new synergies is relatively simple. Each bag synergy is defined as a subtable of Epiphany.Item.THROWING_BAG.SynergyTable containing relevant information about that synergy. While it is possible to add synergies directly to that table, it's recommended to use the provided API function instead:

  function Epiphany.API:AddCainBagSynergy(synergyName: string, synergyTable:table): table

synergyName is the name of the bag synergy. It can be whatever as long as there aren't any conflicts between synergy names, but the convention is "itemname_<bagged|passive>". synergyTable is a table containing bag synergy info. It MUST contain following fields:

All other fields are optional, but needed for the bag to actually do anything. Those are:

The function returns synergyTable.


Bag data

Note: Swinging bag and thrown bag are two separate entities, when a bag is thrown the swinging bag is removed and a flying bag is spawned.

Throwing bags use a bunch of custom entity data variables. It's pointless to list all of them, but here are some that might be useful:

Note: TR Cain's code is extremely convoluted and none of the people who wrote it are working on the mod anymore, so it's possible that some of the information above is incorrect or incomplete. We're going to attempt to untangle it for Wave 5


Object groups + Custom item pools

We split vanilla consumables, slot machines and some other things into thematic groups to have finer control over what types of consumables/slots/etc. can be spawned under specific conditions (for example, Turnover shops only spawn "demonic" beggars and hearts in devil rooms, some randomly rolled consumables can only be souls or essences but not normal runes and so on).
To aid with adding things to those groups, our API provides the following functions:

    function Epiphany.API:AddSlotsToSlotGroup(groupName: string, ...: integer)

The vararg contains variants of all the slot machine IDs that should be added to a given group.
Valid slot groups:

    function Epiphany.API:AddHeartsToHeartGroup(groupName: string, ...: integer)

The vararg contains subtypes of all hearts that should be added to a given group.
Valid heart groups:

    function Epiphany.API:AddItemsToCustomPool(poolName: string, ...: integer|table)

The vararg contains item IDs that should be added to a given pool in one of 2 forms:

Valid custom item pools:

    function Epiphany.API:AddCardsToCardGroup(groupName: string, ...: integer|table)

The vararg contains card IDs that should be added to a given pool in one of 2 forms:

Valid card groups:

Note: Trying to add things to an invalid group does not cause errors, but it also won't do anything (save for printing a warning in console), as our mod only interacts with the groups described above.


Adding item IDs to Tarnished Eden's item blacklist

Tarnished Eden has a list of items that he cannot roll into, as well as a list of items he cannot roll into or out of.

Currently, only the list of items he can't roll into is exposed via the API. You can add items to it like this:

Epiphany.API:AddItemsToEdenBlackList(itemid1, itemid2, itemid3)

The items added via this method will be unable to be accessed via Eden's item rerolls. He will, however, still be able to access these items via all other usual methods, such as finding the item in an item room.


Adding custom Turnover shops

Epiphany.API:AddTurnoverShop({
    Name = "EXAMPLE_SHOP",     -- shop name, is used internally
    Checker = function ()      -- takes 0 arguments, return true if this shop pool should be used
        return IsInExampleShop()
    end,
    ShopLayout =
    {
        ShopKeeperPosition = Vector(320,180), -- optional, defaults to Vector(320, 200)
        SetUpPrice = 15,                      -- optional, defaults to 10

        -- tables at numeric indexes starting from 0 are shop tiers
        -- it's possible to have any amount of shop tiers, as long as their indexes are sequential
        [0] =
        {
          SetUpPrice = 5, -- optional, overrides SetUpPrice in outer scope

          -- first index is always position, second is the item type
          -- pickups are chosen from PickupPool table
          {Vector(280, 320), ShopItemType.Pickup},

          -- collectibles are picked from current room's item pool
          -- unless a getter function is specified at index [3]
          {Vector(360, 320), ShopItemType.Collectible},
          {Vector(360, 360), ShopItemType.Collectible, function (itemPoolType, rng)
            local itemID = rng:RandomInt(CollectibleType.NUM_COLLECTIBLES) + 1
            return itemID
          end},

          -- slots are picked from Slots SlotGroup
          -- unless a getter function is specified at index [3]
          {Vector(360, 400), ShopItemType.Slot},
          {Vector(360, 440), ShopItemType.Slot, function (rng)
            local slotID = rng:RandomInt(9) + 1
            return slotID
          end},

          -- restock machine only spawns if turnover is used by TR Keeper with Birthright
          {Vector(360, 400), ShopItemType.RestockMachine}
        },
        [1] =
        {
          -- ...
        },
    }
    -- PICKUP POOL ENTRY INFO
    -- [1] =
    -- {
    --    [1]        - Pickup variant
    --    [2]        - Pickup subtype
    --                     * may be an int or a function
    --                  * that takes 0 arguments and returns the subtype
    --
    --    minTier - Lowest tier at which a pickup can appear
    --    maxTier - Highest tier at which a pickup can appear
    -- },
    -- Weight  - Pickup's weight
    --                * Higher weight pickups are more likely to spawn
    --                * Pickup's weight is reduced by 4 times for the current shop tier when it spawns
    --
    -- Pickup variant/subtype are required
    -- minTier, maxTier are optional, checks for them pass by default
    -- Weight is optional and defaults to 1.0
    PickupPool =
    {      
      {{ PickupVariant.PICKUP_BOMB, BombSubType.BOMB_NORMAL,                                   minTier = 0, maxTier = 3 }},
      {{ PickupVariant.PICKUP_BOMB, BombSubType.BOMB_DOUBLEPACK,                             minTier = 2, maxTier = 4 }},
      {{ PickupVariant.PICKUP_BOMB, BombSubType.BOMB_GIGA,                                     minTier = 2, maxTier = 4 },  Weight = 0.06},
      {{ PickupVariant.PICKUP_BOMB, BombSubType.BOMB_GOLDEN,                                   minTier = 2, maxTier = 4 },  Weight = 0.03},
      {{ PickupVariant.PICKUP_KEY,  KeySubType.KEY_NORMAL,                                  minTier = 0, maxTier = 3 }},
    }
})

Achievement Animations

The Epiphany API has the capability to show any png image as an achievement. There are two different variants of this. They are:

Epiphany.API:ShowTarnishedAchievement("gfx/ui/achievement/example.png")
Epiphany.API:ShowVanillaAchievement("gfx/ui/achievement/example.png")

The Tarnished variant uses this paper background:

tarnished_achpaper

Whereas the Vanilla variant uses this paper background:

vanilla_achpaper

The PNG image used in the function must be placed in the resources folder of your mod


Tarnished Character Unlock Conventions

Completion Mark Convention

Tarnished Characters unlocks follow a formula similiar to Tainted Characters, however we add our own twist to it. This obviously doesn't need to be exactly followed if you don't want to, but this serves as a framework for those that want to follow our process when designing unlocks They are the following:

Essence/Card Design

Essence try to follow the design of all 3 the iterations of the character they represent The bottle can be shaped however it most suits the character but it should be opaque to not show directly the liquid The liquid should not be seen directly, but it can be hinted at with a puff of smoke coming out of the bottle

"Reversed" Playing Cards that we add are called House Suit Cards internally, Mechanically they follow the same design as Reversed cards, trying to reverse the effect of the card they are based on Visually they only retain the nomenclature "Name of the Card" + "?" They use a custom back that can be applied in the entities2.xml as long Epiphany is loaded anm2path="house suit card.anm2" the front of the card is an Isaac themed card, the 4 Suits are the 4 main pickups of isaac and have a themed "Taint" on them Clubs are Bombs and have burned/explosion marks left on them Spades are Keys and have blood splatters Diamonds are coins and they have a Midas Cursed (Golden) design Hearts are... hearts, and have a bit rot over them