Skip to content

Familiars

Author(s): benevolusgoat
Tags:

Crash course table of contents

The crash course is still a work in progress!

  1. Creating a mod.
    1. Creating the mod.
    2. Uploading a mod.
  2. Creating a passive item.
  3. Creating an active item.
  4. Adding to item pools.
  5. Making costumes.
  6. Creating a character.
  7. Creating entities.
    1. Entity basics.
    2. Creating effects.
    3. Creating enemies.
    4. Creating a familiar.
  8. Adding sounds.
  9. Adding music.
  10. Making pocket items.
    1. Creating custom cards, runes, and objects.
    2. Creating custom pills.
  11. Creating challenges.
  12. Structuring your mod.
  13. Conclusion.
Work in progress!

This article is a work in progress! Some sections may be incomplete or need revising.

Familiars are small companions Isaac obtains, normally through passive items, that follow and assist Isaac. This tutorial will cover the basics of creating a familiar that can shoot tears.

Video tutorial⚓︎

Familiars | Youtube Tutorial

Creating your familiar⚓︎

Creating your familiar involves entries within two different files: items.xml and entities2.xml. Adjustments are needed to cater these entries towards familiars specifically.

items.xml⚓︎

When defining your item, instead of the passive tag, use familiar. This will categorize the item as a familiar item. Normally, an item that summons a familiar should have cache="familiars" present, but items that are defined as a familiar item will automatically handle this.

This items.xml entry will add an item named "Friend Frankie", a quality 1 item with four tags present.

  • baby will have the item contribute to the Conjoined transformation.
  • offensive makes this item available to Tainted Lost.
  • summonable allows the item to be summoned by Lemegeton.
  • monstermanual allows the item's familiar to be summoned by Monster Manual.
1
2
3
<items gfxroot="gfx/items/" version="1">
    <familiar name="Friend Frankie" description="Friends 'till the end" quality="1" tags="baby offensive summonable monstermanual" />
</items>

entities2.xml⚓︎

In conjunction with your item, the familiar must exist as an entity. The id variable must be equal to 3 to define it as a familiar.

This entities2.xml entry will add a familiar entity, aptly named "Friend Frankie", with some simple attributes. Note that the items.xml entry and the entities2.xml entry do not need to have the same name; it is only for convenience.

For the variant, it can be any arbitrary value not taken up by an existing vanilla familiar. Available variants are 131-199, 244-899, and 901-4095. You can also omit the variant entirely to have it auto-assign an available variant.

1
2
3
4
<entities anm2root="gfx/" version="5">
    <entity name="Friend Frankie" id="3" variant="508" anm2path="familiar_friend_frankie.anm2"
    tags="cansacrifice" shadowSize="11" friction="1" />
</entities>

familiar-relevant tags⚓︎

The tags variable has several options available that are exclusive to or relevant for familiars.

tags list
Tag-Name Suffix
cansacrifice Marks familiars that Sacrificial Altar can be used on.
fly Increases familiar size and damage if their player has Hive Mind, similarly to BFFS!.
spider Increases familiar size and damage if their player has Hive Mind, similarly to BFFS!.

REPENTOGON also has a customtags variable intended to expand upon the tags list.

customtags list
Tag-Name Suffix
familiarignorebffs The BFFS! item will no longer affect this familiar.
familiarcantakedamage Familiars with baseHP above 0 will be able to take damage and die, such as from enemy contact, lasers or explosions. Note that they will only take damage from projectiles if they also have the familiarblockprojectiles tag.
familiarblockprojectiles The familiar automatically destroys enemy projectiles on contact.
nocharm Makes the familiar not be charmed by Siren.

Linking the familiar to the item⚓︎

Although you have created your item and your entity, there is nothing that automatically links the two together. This must be done manually through Lua code through the use of the MC_EVALUATE_CACHE callback.

Firstly, set up the main.lua file at the root of your mod folder, and fetch the necessary variables needed and the callback.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
local mod = RegisterMod("My Mod", 1)

--Obtain the ID automatically generated by the game for both your item and your entity.
--You will be storing other variables here that do not change, named constants, later on.
local ITEM_ID = Isaac.GetItemIdByName("Friend Frankie")
local FAMILIAR_VARIANT = Isaac.GetEntityVariantByName("Friend Frankie")

---The player who's cache is being evaluated is the first argument passed into the function.
function mod:EvaluateCache(player)

end

--MC_EVALUATE_CACHE allows an optional argument where you can define a CacheFlag, which will make the callback only run for that flag.
--In this instance, the function will only ever run for the CACHE_FAMILIARS CacheFlag.
mod:AddCallback(ModCallbacks.MC_EVALUATE_CACHE, mod.EvaluateCache, CacheFlag.CACHE_FAMILIARS)

Checking what familiars should spawn, adding familiars, or removing them is all handled through the EntityPlayer:CheckFamiliar function. It is not necessarily required, but is convenient for most use-cases. The function takes the following arguments:

  1. What familiar variant to spawn.
  2. How many familiars are expected to be present.
  3. An RNG object for determining its InitSeed.
  4. An ItemConfigItem object for linking an item to the spawned familiar. This is necessary for Sacrifical Altar to function properly, but is technically optional.
  5. An integer to spawn a specific SubType of familiar. This is optional, and by default spawns familiars with a subtype of 0.

The familiar variant is already stored earlier in the lua file. For the number of familiars to spawn, this can be any number you'd like, but the most common example is how many copies of the item Isaac has combined with the number of TemporaryEffects of the item. The purpose of the latter is for items such as Box of Friends, where it will add a TemporaryEffect of your familiar item to spawn a copy of your familiar without needing to own the item.

CheckFamiliar and RNG

The RNG object passed into CheckFamiliar is used for the familiar's InitSeed, which is often used by modders as a unique identifier for each individual familiar.

CheckFamiliar is bugged in that it will get new seeds from the RNG object provided without actually changing the object. This means that if you use CheckFamiliar to spawn 3 new familiars at once, they will all have different seeds, but the RNG object will still have the seed it started with. However, if you use CheckFamiliar to spawn one familiar 3 separate times (like Box of Friends), they'll all have the same seed.

A simple way to fix this is to either advance the RNG object manually for each familiar spawned, or to just provide a unique RNG object to CheckFamiliar every time you use it. This tutorial will use the latter solution for simplicity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
local mod = RegisterMod("My Mod", 1)

local ITEM_ID = Isaac.GetItemIdByName("Friend Frankie")
local FAMILIAR_VARIANT = Isaac.GetEntityVariantByName("Friend Frankie")
--Storing constant variable here for convenience.
local RNG_SHIFT_INDEX = 35
--Access the ItemConfig class.
local itemConfig = Isaac.GetItemConfig()
--Obtain the ItemConfigItem object for our item.
local CONFIG_FRANKIE = itemConfig:GetCollectible(ITEM_ID)

function mod:EvaluateCache(player)
    --Access the TemporaryEffects class.
    local effects = player:GetEffects()
    --Get how many familiars should be spawned.
    local count = player:GetCollectibleNum(ITEM_ID) + effects:GetCollectibleEffectNum(ITEM_ID)
    --Create a new RNG object.
    local rng = RNG()
    --Get a random seed via Random(), which returns any number from 0 to 2^32, inclusive.
    --RNG objects with a seed of 0 will crash the game, so math.max is used to ensure it's always at least 1 or above.
    local seed = math.max(Random(), 1)
    --When setting a seed, you must set the "Shift Index", which essentially determines the sequence of numbers the RNG object will have.
    --The most commonly used Shift Index in vanilla was revealed to be 35 by one of the game's developers.
    rng:SetSeed(seed, RNG_SHIFT_INDEX)

    player:CheckFamiliar(FAMILIAR_VARIANT, count, rng, CONFIG_FRANKIE)
end

mod:AddCallback(ModCallbacks.MC_EVALUATE_CACHE, mod.EvaluateCache, CacheFlag.CACHE_FAMILIARS)

With that, your familiar should now spawn when collecting your item and through other scenarios such as Box of Friends!

Creating a follower familiar⚓︎

Your familiar exists, but at the current moment will do nothing when spawned in. Let's have it follow the player.

This code will automatically make the familiar a "follower" familiar, being inserted into the familiar line and following the player/the familiar in front of them in line.

Code snippets from here on are in the context of being placed below the previous code snippets.

1
2
3
4
5
6
7
--MC_FAMILIAR_UPDATE passes the familiar its updating.
function mod:FamiliarUpdate(familiar)
    familiar:FollowParent()
end

--MC_FAMILIAR_UPDATE is the UPDATE callback for familiars. It accepts an optional argument to only run for your familiar variant.
mod:AddCallback(ModCallbacks.MC_FAMILIAR_UPDATE, mod.FamiliarUpdate, FAMILIAR_VARIANT)

A shooter familiar is a familiar that fires tears alongside Isaac. Thankfully there is an extremely simple function that will handle the vast majority of work involved: EntityFamiliar:Shoot().

1
2
3
4
5
6
function mod:FamiliarUpdate(familiar)
    familiar:Shoot()
    familiar:FollowParent()
end

mod:AddCallback(ModCallbacks.MC_FAMILIAR_UPDATE, mod.FamiliarUpdate, FAMILIAR_VARIANT)

This function, when ran on MC_FAMILIAR_UPDATE, will make your familiar fire regular tears 1.36 times a second dealing 3.5 damage per shot. It will automatically synergize with shooter familiar modifiers such as Baby-Bender, BFFS!, and Forgotten Lullaby, and firing direction overrides like King Baby and Marked. It will also automatically handle playing the appropriate animations for a shooter familiar. You can view 003.001_brother bobby.anm2 inside the game's extracted resources for reference.

If you wish to modify the firing speed and attributes of the tear, there are methods for doing so. Without REPENTOGON, you will need to some manual checks inside MC_FAMILIAR_UPDATE. This code will alter the firerate of the familiar so that it fires 1 tear per second, do double the normal damage, and slow enemies.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
--Define the desired FireCooldown for the familiar.
--30 is equivalent to 1 second.
local NEW_COOLDOWN = 30
--By default, FireCooldown is set to 22 when firing a tear.
local DEFAULT_COOLDOWN = 22
--This is kept as a "percentage" of how much the cooldown is to be adjusted by.
--so that Forgotten Lullaby is accounted for with its adjustment to the default cooldown.
local COOLDOWN_MULT = NEW_COOLDOWN / DEFAULT_COOLDOWN

function mod:FamiliarUpdate(familiar)
    local player = familiar.Player
    local fireDir = player:GetFireDirection()
    --Check FireCooldown before :Shoot() adjusts it as familiars can only fire if its 0.
    --It's <= 1 specifically because :Shoot() will subtract FireCooldown on its own, check if its 0, then fire again if so.
    local canFire = familiar.FireCooldown <= 1

    familiar:Shoot()

    --Checking FireCooldown > 0, :Shoot() should have successfully fired a tear.
    if canFire and familiar.FireCooldown > 0 then
        --Set new cooldown. It's under math.floor to turn it into an integer, as FireCooldown doesn't accept floats.
        familiar.FireCooldown = math.floor(familiar.FireCooldown * COOLDOWN_MULT)

        --:Shoot() will already have spawned the tear. Search for the tear with Isaac.FindByType to make modifications to it.
        --MC_POST_TEAR_INIT isn't reliable as tear effects/damage are overridden afterwards.
        for _, ent in ipairs(Isaac.FindByType(EntityType.ENTITY_TEAR, TearVariant.BLUE, 0)) do
            --Check its only from our familiar by checking the Type and Variant of what spawned it.
            if ent.SpawnerType == EntityType.ENTITY_FAMILIAR
                and ent.SpawnerVariant == FAMILIAR_VARIANT
                --Check that it just spawned.
                and tear.FrameCount == 0
            then
                --Isaac.FindByType always passes an Entity object. Make it an EntityFamiliar to access :AddTearFlags().
                local tear = ent:ToTear()
                --Add tear modifiers.
                tear:AddTearFlags(TearFlags.TEAR_SLOW)
                tear.CollisionDamage = tear.CollisionDamage * 2
            end
        end
    end

    familiar:FollowParent()
end

mod:AddCallback(ModCallbacks.MC_FAMILIAR_UPDATE, mod.FamiliarUpdate, FAMILIAR_VARIANT)

If using REPENTOGON, you can instead use a callback named MC_POST_FAMILIAR_FIRE_PROJECTILE that triggers when EntityFamiliar:Shoot is called and a tear is fired. You can adjust properties of the tear there yourself.

1
2
3
4
5
6
7
8
--Callback only passes the fired tear
function mod:FamiliarShoot(tear)
    tear:AddTearFlags(TearFlags.TEAR_SLOW)
    tear.CollisionDamage = tear.CollisionDamage * 2
end

--Callback accepts an optional argument to only run for your familiar variant.
mod:AddCallback(ModCallbacks.MC_POST_FAMILIAR_FIRE_PROJECTILE, mod.FamiliarShoot, FAMILIAR_VARIANT)

As a final touch, REPENTOGON also allows you to assign the priority of a familiar through MC_GET_FOLLOWER_PRIORITY. With FollowerPriority.SHOOTER, the familiar is further back in the line, but is in front of any other familiars without an assigned priority. It also affects how the familiar is treated for Lilith and Tainted Lilith's birthright.

1
2
3
4
5
6
7
--Don't need to do anything other than return the new priority as it only runs for our familiar.
function mod:FamilarPriority()
    return FollowerPriority.SHOOTER
end

--Callback accepts an optional argument to only run for your familiar variant.
mod:AddCallback(ModCallbacks.MC_GET_FOLLOWER_PRIORITY, mod.FamiliarPriority, FAMILIAR_VARIANT)